Index: /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/XML_Unserializer.php =================================================================== --- /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/XML_Unserializer.php (revision 4538) +++ /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/XML_Unserializer.php (revision 4538) @@ -0,0 +1,856 @@ + + * @copyright 1997-2005 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version CVS: $Id: Unserializer.php,v 1.39 2005/09/28 11:19:56 schst Exp $ + * @link http://pear.php.net/package/XML_Serializer + * @see XML_Unserializer + */ + +/** + * uses PEAR error managemt + */ +//require_once 'PEAR.php'; + +/** + * uses XML_Parser to unserialize document + */ +//require_once 'XML/Parser.php'; + +/** + * option: Convert nested tags to array or object + * + * Possible values: + * - array + * - object + * - associative array to define this option per tag name + */ +define('XML_UNSERIALIZER_OPTION_COMPLEXTYPE', 'complexType'); + +/** + * option: Name of the attribute that stores the original key + * + * Possible values: + * - any string + */ +define('XML_UNSERIALIZER_OPTION_ATTRIBUTE_KEY', 'keyAttribute'); + +/** + * option: Name of the attribute that stores the type + * + * Possible values: + * - any string + */ +define('XML_UNSERIALIZER_OPTION_ATTRIBUTE_TYPE', 'typeAttribute'); + +/** + * option: Name of the attribute that stores the class name + * + * Possible values: + * - any string + */ +define('XML_UNSERIALIZER_OPTION_ATTRIBUTE_CLASS', 'classAttribute'); + +/** + * option: Whether to use the tag name as a class name + * + * Possible values: + * - true or false + */ +define('XML_UNSERIALIZER_OPTION_TAG_AS_CLASSNAME', 'tagAsClass'); + +/** + * option: Name of the default class + * + * Possible values: + * - any string + */ +define('XML_UNSERIALIZER_OPTION_DEFAULT_CLASS', 'defaultClass'); + +/** + * option: Whether to parse attributes + * + * Possible values: + * - true or false + */ +define('XML_UNSERIALIZER_OPTION_ATTRIBUTES_PARSE', 'parseAttributes'); + +/** + * option: Key of the array to store attributes (if any) + * + * Possible values: + * - any string + * - false (disabled) + */ +define('XML_UNSERIALIZER_OPTION_ATTRIBUTES_ARRAYKEY', 'attributesArray'); + +/** + * option: string to prepend attribute name (if any) + * + * Possible values: + * - any string + * - false (disabled) + */ +define('XML_UNSERIALIZER_OPTION_ATTRIBUTES_PREPEND', 'prependAttributes'); + +/** + * option: key to store the content, if XML_UNSERIALIZER_OPTION_ATTRIBUTES_PARSE is used + * + * Possible values: + * - any string + */ +define('XML_UNSERIALIZER_OPTION_CONTENT_KEY', 'contentName'); + +/** + * option: map tag names + * + * Possible values: + * - associative array + */ +define('XML_UNSERIALIZER_OPTION_TAG_MAP', 'tagMap'); + +/** + * option: list of tags that will always be enumerated + * + * Possible values: + * - indexed array + */ +define('XML_UNSERIALIZER_OPTION_FORCE_ENUM', 'forceEnum'); + +/** + * option: Encoding of the XML document + * + * Possible values: + * - UTF-8 + * - ISO-8859-1 + */ +define('XML_UNSERIALIZER_OPTION_ENCODING_SOURCE', 'encoding'); + +/** + * option: Desired target encoding of the data + * + * Possible values: + * - UTF-8 + * - ISO-8859-1 + */ +define('XML_UNSERIALIZER_OPTION_ENCODING_TARGET', 'targetEncoding'); + +/** + * option: Callback that will be applied to textual data + * + * Possible values: + * - any valid PHP callback + */ +define('XML_UNSERIALIZER_OPTION_DECODE_FUNC', 'decodeFunction'); + +/** + * option: whether to return the result of the unserialization from unserialize() + * + * Possible values: + * - true + * - false (default) + */ +define('XML_UNSERIALIZER_OPTION_RETURN_RESULT', 'returnResult'); + +/** + * option: set the whitespace behaviour + * + * Possible values: + * - XML_UNSERIALIZER_WHITESPACE_KEEP + * - XML_UNSERIALIZER_WHITESPACE_TRIM + * - XML_UNSERIALIZER_WHITESPACE_NORMALIZE + */ +define('XML_UNSERIALIZER_OPTION_WHITESPACE', 'whitespace'); + +/** + * Keep all whitespace + */ +define('XML_UNSERIALIZER_WHITESPACE_KEEP', 'keep'); + +/** + * remove whitespace from start and end of the data + */ +define('XML_UNSERIALIZER_WHITESPACE_TRIM', 'trim'); + +/** + * normalize whitespace + */ +define('XML_UNSERIALIZER_WHITESPACE_NORMALIZE', 'normalize'); + +/** + * option: whether to ovverride all options that have been set before + * + * Possible values: + * - true + * - false (default) + */ +define('XML_UNSERIALIZER_OPTION_OVERRIDE_OPTIONS', 'overrideOptions'); + +/** + * option: list of tags, that will not be used as keys + */ +define('XML_UNSERIALIZER_OPTION_IGNORE_KEYS', 'ignoreKeys'); + +/** + * option: whether to use type guessing for scalar values + */ +define('XML_UNSERIALIZER_OPTION_GUESS_TYPES', 'guessTypes'); + +/** + * error code for no serialization done + */ +define('XML_UNSERIALIZER_ERROR_NO_UNSERIALIZATION', 151); + +/** + * XML_Unserializer + * + * class to unserialize XML documents that have been created with + * XML_Serializer. To unserialize an XML document you have to add + * type hints to the XML_Serializer options. + * + * If no type hints are available, XML_Unserializer will guess how + * the tags should be treated, that means complex structures will be + * arrays and tags with only CData in them will be strings. + * + * + * require_once 'XML/Unserializer.php'; + * + * // be careful to always use the ampersand in front of the new operator + * $unserializer = &new XML_Unserializer(); + * + * $unserializer->unserialize($xml); + * + * $data = $unserializer->getUnserializedData(); + * + * + * + * @category XML + * @package XML_Serializer + * @author Stephan Schmidt + * @copyright 1997-2005 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version Release: 0.18.0 + * @link http://pear.php.net/package/XML_Serializer + * @see XML_Serializer + */ +class XML_Unserializer extends PEAR +{ + /** + * list of all available options + * + * @access private + * @var array + */ + var $_knownOptions = array( + XML_UNSERIALIZER_OPTION_COMPLEXTYPE, + XML_UNSERIALIZER_OPTION_ATTRIBUTE_KEY, + XML_UNSERIALIZER_OPTION_ATTRIBUTE_TYPE, + XML_UNSERIALIZER_OPTION_ATTRIBUTE_CLASS, + XML_UNSERIALIZER_OPTION_TAG_AS_CLASSNAME, + XML_UNSERIALIZER_OPTION_DEFAULT_CLASS, + XML_UNSERIALIZER_OPTION_ATTRIBUTES_PARSE, + XML_UNSERIALIZER_OPTION_ATTRIBUTES_ARRAYKEY, + XML_UNSERIALIZER_OPTION_ATTRIBUTES_PREPEND, + XML_UNSERIALIZER_OPTION_CONTENT_KEY, + XML_UNSERIALIZER_OPTION_TAG_MAP, + XML_UNSERIALIZER_OPTION_FORCE_ENUM, + XML_UNSERIALIZER_OPTION_ENCODING_SOURCE, + XML_UNSERIALIZER_OPTION_ENCODING_TARGET, + XML_UNSERIALIZER_OPTION_DECODE_FUNC, + XML_UNSERIALIZER_OPTION_RETURN_RESULT, + XML_UNSERIALIZER_OPTION_WHITESPACE, + XML_UNSERIALIZER_OPTION_IGNORE_KEYS, + XML_UNSERIALIZER_OPTION_GUESS_TYPES + ); + /** + * default options for the serialization + * + * @access private + * @var array + */ + var $_defaultOptions = array( + XML_UNSERIALIZER_OPTION_COMPLEXTYPE => 'array', // complex types will be converted to arrays, if no type hint is given + XML_UNSERIALIZER_OPTION_ATTRIBUTE_KEY => '_originalKey', // get array key/property name from this attribute + XML_UNSERIALIZER_OPTION_ATTRIBUTE_TYPE => '_type', // get type from this attribute + XML_UNSERIALIZER_OPTION_ATTRIBUTE_CLASS => '_class', // get class from this attribute (if not given, use tag name) + XML_UNSERIALIZER_OPTION_TAG_AS_CLASSNAME => true, // use the tagname as the classname + XML_UNSERIALIZER_OPTION_DEFAULT_CLASS => 'stdClass', // name of the class that is used to create objects + XML_UNSERIALIZER_OPTION_ATTRIBUTES_PARSE => false, // parse the attributes of the tag into an array + XML_UNSERIALIZER_OPTION_ATTRIBUTES_ARRAYKEY => false, // parse them into sperate array (specify name of array here) + XML_UNSERIALIZER_OPTION_ATTRIBUTES_PREPEND => '', // prepend attribute names with this string + XML_UNSERIALIZER_OPTION_CONTENT_KEY => '_content', // put cdata found in a tag that has been converted to a complex type in this key + XML_UNSERIALIZER_OPTION_TAG_MAP => array(), // use this to map tagnames + XML_UNSERIALIZER_OPTION_FORCE_ENUM => array(), // these tags will always be an indexed array + XML_UNSERIALIZER_OPTION_ENCODING_SOURCE => null, // specify the encoding character of the document to parse + XML_UNSERIALIZER_OPTION_ENCODING_TARGET => null, // specify the target encoding + XML_UNSERIALIZER_OPTION_DECODE_FUNC => null, // function used to decode data + XML_UNSERIALIZER_OPTION_RETURN_RESULT => false, // unserialize() returns the result of the unserialization instead of true + XML_UNSERIALIZER_OPTION_WHITESPACE => XML_UNSERIALIZER_WHITESPACE_TRIM, // remove whitespace around data + XML_UNSERIALIZER_OPTION_IGNORE_KEYS => array(), // List of tags that will automatically be added to the parent, instead of adding a new key + XML_UNSERIALIZER_OPTION_GUESS_TYPES => false // Whether to use type guessing + ); + + /** + * current options for the serialization + * + * @access public + * @var array + */ + var $options = array(); + + /** + * unserialized data + * + * @access private + * @var string + */ + var $_unserializedData = null; + + /** + * name of the root tag + * + * @access private + * @var string + */ + var $_root = null; + + /** + * stack for all data that is found + * + * @access private + * @var array + */ + var $_dataStack = array(); + + /** + * stack for all values that are generated + * + * @access private + * @var array + */ + var $_valStack = array(); + + /** + * current tag depth + * + * @access private + * @var int + */ + var $_depth = 0; + + /** + * XML_Parser instance + * + * @access private + * @var object XML_Parser + */ + var $_parser = null; + + /** + * constructor + * + * @access public + * @param mixed $options array containing options for the unserialization + */ + function XML_Unserializer($options = null) + { + if (is_array($options)) { + $this->options = array_merge($this->_defaultOptions, $options); + } else { + $this->options = $this->_defaultOptions; + } + } + + /** + * return API version + * + * @access public + * @static + * @return string $version API version + */ + function apiVersion() + { + return '0.18.0'; + } + + /** + * reset all options to default options + * + * @access public + * @see setOption(), XML_Unserializer(), setOptions() + */ + function resetOptions() + { + $this->options = $this->_defaultOptions; + } + + /** + * set an option + * + * You can use this method if you do not want to set all options in the constructor + * + * @access public + * @see resetOption(), XML_Unserializer(), setOptions() + */ + function setOption($name, $value) + { + $this->options[$name] = $value; + } + + /** + * sets several options at once + * + * You can use this method if you do not want to set all options in the constructor + * + * @access public + * @see resetOption(), XML_Unserializer(), setOption() + */ + function setOptions($options) + { + $this->options = array_merge($this->options, $options); + } + + /** + * unserialize data + * + * @access public + * @param mixed $data data to unserialize (string, filename or resource) + * @param boolean $isFile data should be treated as a file + * @param array $options options that will override the global options for this call + * @return boolean $success + */ + function unserialize($data, $isFile = false, $options = null) + { + $this->_unserializedData = null; + $this->_root = null; + + // if options have been specified, use them instead + // of the previously defined ones + if (is_array($options)) { + $optionsBak = $this->options; + if (isset($options[XML_UNSERIALIZER_OPTION_OVERRIDE_OPTIONS]) && $options[XML_UNSERIALIZER_OPTION_OVERRIDE_OPTIONS] == true) { + $this->options = array_merge($this->_defaultOptions, $options); + } else { + $this->options = array_merge($this->options, $options); + } + } else { + $optionsBak = null; + } + + $this->_valStack = array(); + $this->_dataStack = array(); + $this->_depth = 0; + + $this->_createParser(); + + if (is_string($data)) { + if ($isFile) { + $result = $this->_parser->setInputFile($data); + if (PEAR::isError($result)) { + return $result; + } + $result = $this->_parser->parse(); + } else { + $result = $this->_parser->parseString($data,true); + } + } else { + $this->_parser->setInput($data); + $result = $this->_parser->parse(); + } + + if ($this->options[XML_UNSERIALIZER_OPTION_RETURN_RESULT] === true) { + $return = $this->_unserializedData; + } else { + $return = true; + } + + if ($optionsBak !== null) { + $this->options = $optionsBak; + } + + if (PEAR::isError($result)) { + return $result; + } + + return $return; + } + + /** + * get the result of the serialization + * + * @access public + * @return string $serializedData + */ + function getUnserializedData() + { + if ($this->_root === null) { + return $this->raiseError('No unserialized data available. Use XML_Unserializer::unserialize() first.', XML_UNSERIALIZER_ERROR_NO_UNSERIALIZATION); + } + return $this->_unserializedData; + } + + /** + * get the name of the root tag + * + * @access public + * @return string $rootName + */ + function getRootName() + { + if ($this->_root === null) { + return $this->raiseError('No unserialized data available. Use XML_Unserializer::unserialize() first.', XML_UNSERIALIZER_ERROR_NO_UNSERIALIZATION); + } + return $this->_root; + } + + /** + * Start element handler for XML parser + * + * @access private + * @param object $parser XML parser object + * @param string $element XML element + * @param array $attribs attributes of XML tag + * @return void + */ + function startHandler($parser, $element, $attribs) + { + if (isset($attribs[$this->options[XML_UNSERIALIZER_OPTION_ATTRIBUTE_TYPE]])) { + $type = $attribs[$this->options[XML_UNSERIALIZER_OPTION_ATTRIBUTE_TYPE]]; + $guessType = false; + } else { + $type = 'string'; + if ($this->options[XML_UNSERIALIZER_OPTION_GUESS_TYPES] === true) { + $guessType = true; + } else { + $guessType = false; + } + } + + if ($this->options[XML_UNSERIALIZER_OPTION_DECODE_FUNC] !== null) { + $attribs = array_map($this->options[XML_UNSERIALIZER_OPTION_DECODE_FUNC], $attribs); + } + + $this->_depth++; + $this->_dataStack[$this->_depth] = null; + + if (is_array($this->options[XML_UNSERIALIZER_OPTION_TAG_MAP]) && isset($this->options[XML_UNSERIALIZER_OPTION_TAG_MAP][$element])) { + $element = $this->options[XML_UNSERIALIZER_OPTION_TAG_MAP][$element]; + } + + $val = array( + 'name' => $element, + 'value' => null, + 'type' => $type, + 'guessType' => $guessType, + 'childrenKeys' => array(), + 'aggregKeys' => array() + ); + + if ($this->options[XML_UNSERIALIZER_OPTION_ATTRIBUTES_PARSE] == true && (count($attribs) > 0)) { + $val['children'] = array(); + $val['type'] = $this->_getComplexType($element); + $val['class'] = $element; + + if ($this->options[XML_UNSERIALIZER_OPTION_GUESS_TYPES] === true) { + $attribs = $this->_guessAndSetTypes($attribs); + } + if ($this->options[XML_UNSERIALIZER_OPTION_ATTRIBUTES_ARRAYKEY] != false) { + $val['children'][$this->options[XML_UNSERIALIZER_OPTION_ATTRIBUTES_ARRAYKEY]] = $attribs; + } else { + foreach ($attribs as $attrib => $value) { + $val['children'][$this->options[XML_UNSERIALIZER_OPTION_ATTRIBUTES_PREPEND].$attrib] = $value; + } + } + } + + $keyAttr = false; + + if (is_string($this->options[XML_UNSERIALIZER_OPTION_ATTRIBUTE_KEY])) { + $keyAttr = $this->options[XML_UNSERIALIZER_OPTION_ATTRIBUTE_KEY]; + } elseif (is_array($this->options[XML_UNSERIALIZER_OPTION_ATTRIBUTE_KEY])) { + if (isset($this->options[XML_UNSERIALIZER_OPTION_ATTRIBUTE_KEY][$element])) { + $keyAttr = $this->options[XML_UNSERIALIZER_OPTION_ATTRIBUTE_KEY][$element]; + } elseif (isset($this->options[XML_UNSERIALIZER_OPTION_ATTRIBUTE_KEY]['#default'])) { + $keyAttr = $this->options[XML_UNSERIALIZER_OPTION_ATTRIBUTE_KEY]['#default']; + } elseif (isset($this->options[XML_UNSERIALIZER_OPTION_ATTRIBUTE_KEY]['__default'])) { + // keep this for BC + $keyAttr = $this->options[XML_UNSERIALIZER_OPTION_ATTRIBUTE_KEY]['__default']; + } + } + + if ($keyAttr !== false && isset($attribs[$keyAttr])) { + $val['name'] = $attribs[$keyAttr]; + } + + if (isset($attribs[$this->options[XML_UNSERIALIZER_OPTION_ATTRIBUTE_CLASS]])) { + $val['class'] = $attribs[$this->options[XML_UNSERIALIZER_OPTION_ATTRIBUTE_CLASS]]; + } + + array_push($this->_valStack, $val); + } + + /** + * Try to guess the type of several values and + * set them accordingly + * + * @access private + * @param array array, containing the values + * @return array array, containing the values with their correct types + */ + function _guessAndSetTypes($array) + { + foreach ($array as $key => $value) { + $array[$key] = $this->_guessAndSetType($value); + } + return $array; + } + + /** + * Try to guess the type of a value and + * set it accordingly + * + * @access private + * @param string character data + * @return mixed value with the best matching type + */ + function _guessAndSetType($value) + { + if ($value === 'true') { + return true; + } + if ($value === 'false') { + return false; + } + if ($value === 'NULL') { + return null; + } + if (preg_match('/^[-+]?[0-9]{1,}$/', $value)) { + return intval($value); + } + if (preg_match('/^[-+]?[0-9]{1,}\.[0-9]{1,}$/', $value)) { + return doubleval($value); + } + return (string)$value; + } + + /** + * End element handler for XML parser + * + * @access private + * @param object XML parser object + * @param string + * @return void + */ + function endHandler($parser, $element) + { + $value = array_pop($this->_valStack); + switch ($this->options[XML_UNSERIALIZER_OPTION_WHITESPACE]) { + case XML_UNSERIALIZER_WHITESPACE_KEEP: + $data = $this->_dataStack[$this->_depth]; + break; + case XML_UNSERIALIZER_WHITESPACE_NORMALIZE: + $data = trim(preg_replace('/\s\s+/m', ' ', $this->_dataStack[$this->_depth])); + break; + case XML_UNSERIALIZER_WHITESPACE_TRIM: + default: + $data = trim($this->_dataStack[$this->_depth]); + break; + } + + // adjust type of the value + switch(strtolower($value['type'])) { + + // unserialize an object + case 'object': + if (isset($value['class'])) { + $classname = $value['class']; + } else { + $classname = ''; + } + // instantiate the class + if ($this->options[XML_UNSERIALIZER_OPTION_TAG_AS_CLASSNAME] === true && class_exists($classname)) { + $value['value'] = &new $classname; + } else { + $value['value'] = &new $this->options[XML_UNSERIALIZER_OPTION_DEFAULT_CLASS]; + } + if (trim($data) !== '') { + if ($value['guessType'] === true) { + $data = $this->_guessAndSetType($data); + } + $value['children'][$this->options[XML_UNSERIALIZER_OPTION_CONTENT_KEY]] = $data; + } + + // set properties + foreach ($value['children'] as $prop => $propVal) { + // check whether there is a special method to set this property + $setMethod = 'set'.$prop; + if (method_exists($value['value'], $setMethod)) { + call_user_func(array(&$value['value'], $setMethod), $propVal); + } else { + $value['value']->$prop = $propVal; + } + } + // check for magic function + if (method_exists($value['value'], '__wakeup')) { + $value['value']->__wakeup(); + } + break; + + // unserialize an array + case 'array': + if (trim($data) !== '') { + if ($value['guessType'] === true) { + $data = $this->_guessAndSetType($data); + } + $value['children'][$this->options[XML_UNSERIALIZER_OPTION_CONTENT_KEY]] = $data; + } + if (isset($value['children'])) { + $value['value'] = $value['children']; + } else { + $value['value'] = array(); + } + break; + + // unserialize a null value + case 'null': + $data = null; + break; + + // unserialize a resource => this is not possible :-( + case 'resource': + $value['value'] = $data; + break; + + // unserialize any scalar value + default: + if ($value['guessType'] === true) { + $data = $this->_guessAndSetType($data); + } else { + settype($data, $value['type']); + } + + $value['value'] = $data; + break; + } + $parent = array_pop($this->_valStack); + if ($parent === null) { + $this->_unserializedData = &$value['value']; + $this->_root = &$value['name']; + return true; + } else { + // parent has to be an array + if (!isset($parent['children']) || !is_array($parent['children'])) { + $parent['children'] = array(); + if (!in_array($parent['type'], array('array', 'object'))) { + $parent['type'] = $this->_getComplexType($parent['name']); + if ($parent['type'] == 'object') { + $parent['class'] = $parent['name']; + } + } + } + + if (in_array($element, $this->options[XML_UNSERIALIZER_OPTION_IGNORE_KEYS])) { + $ignoreKey = true; + } else { + $ignoreKey = false; + } + + if (!empty($value['name']) && $ignoreKey === false) { + // there already has been a tag with this name + if (in_array($value['name'], $parent['childrenKeys']) || in_array($value['name'], $this->options[XML_UNSERIALIZER_OPTION_FORCE_ENUM])) { + // no aggregate has been created for this tag + if (!in_array($value['name'], $parent['aggregKeys'])) { + if (isset($parent['children'][$value['name']])) { + $parent['children'][$value['name']] = array($parent['children'][$value['name']]); + } else { + $parent['children'][$value['name']] = array(); + } + array_push($parent['aggregKeys'], $value['name']); + } + array_push($parent['children'][$value['name']], $value['value']); + } else { + $parent['children'][$value['name']] = &$value['value']; + array_push($parent['childrenKeys'], $value['name']); + } + } else { + array_push($parent['children'], $value['value']); + } + array_push($this->_valStack, $parent); + } + + $this->_depth--; + } + + /** + * Handler for character data + * + * @access private + * @param object XML parser object + * @param string CDATA + * @return void + */ + function cdataHandler($parser, $cdata) + { + if ($this->options[XML_UNSERIALIZER_OPTION_DECODE_FUNC] !== null) { + $cdata = call_user_func($this->options[XML_UNSERIALIZER_OPTION_DECODE_FUNC], $cdata); + } + $this->_dataStack[$this->_depth] .= $cdata; + } + + /** + * get the complex type, that should be used for a specified tag + * + * @access private + * @param string name of the tag + * @return string complex type ('array' or 'object') + */ + function _getComplexType($tagname) + { + if (is_string($this->options[XML_UNSERIALIZER_OPTION_COMPLEXTYPE])) { + return $this->options[XML_UNSERIALIZER_OPTION_COMPLEXTYPE]; + } + if (isset($this->options[XML_UNSERIALIZER_OPTION_COMPLEXTYPE][$tagname])) { + return $this->options[XML_UNSERIALIZER_OPTION_COMPLEXTYPE][$tagname]; + } + if (isset($this->options[XML_UNSERIALIZER_OPTION_COMPLEXTYPE]['#default'])) { + return $this->options[XML_UNSERIALIZER_OPTION_COMPLEXTYPE]['#default']; + } + return 'array'; + } + + /** + * create the XML_Parser instance + * + * @access private + * @return boolean + */ + function _createParser() + { + if (is_object($this->_parser)) { + $this->_parser->free(); + unset($this->_parser); + } + $this->_parser = &new XML_Parser($this->options[XML_UNSERIALIZER_OPTION_ENCODING_SOURCE], 'event', $this->options[XML_UNSERIALIZER_OPTION_ENCODING_TARGET]); + $this->_parser->folding = false; + $this->_parser->setHandlerObj($this); + return true; + } +} +?> Index: /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/page.devices.php =================================================================== --- /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/page.devices.php (revision 2617) +++ /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/page.devices.php (revision 2617) @@ -0,0 +1,20 @@ + +
+ +
Index: /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/module.xml =================================================================== --- /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/module.xml (revision 6609) +++ /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/module.xml (revision 6609) @@ -0,0 +1,52 @@ + + core + setup + Basic + Core + 2.5.0rc3.0 + no + no + + *2.5.0rc3.0* #3150, #3152, #3142, #3167, #3077 (DAHDI Support) + *2.5.0rc2.5* #3129, #3144, #3146 and some streamling of dialout-trunk and outbound-callerid + *2.5.0rc2.4* #3128, #3140 sqlite3 fix and fixed typo introduced in dialparties.agi + *2.5.0rc2.3* #3130, #2750, #3128, #3131 (requires r6519) added master recording disable (can be performance booster) + *2.5.0rc2.2* lots of changed to be updated + *2.5.0rc2.1* #3115, #3116 remove warning and add t option to nv_faxdetect + *2.5.0rc2.0* #3091, #3099, #3100 Added return from voicemail/busy to IVR support, added CID Priority Route Inbound Route option + *2.5.0rc1.1* #3060, #3072, #3078 mixmonitor recording dir typo, move disallow=all to top of all sip, iax sections, file call screening nomemory + *2.5.0rc1.0* #3053 allow routing in CIDs like Private, Blocked, Restricted, etc. and bump to rc1 + *2.5.0beta1.4* #3046, #3019, #3047, #3040 + *2.5.0beta1.3* #2530 typo _GLOBALS should be GLOBALS, clean up conflicts when creating users/extensions + *2.5.0beta1.2* #2913 Add pre-answer delay option to incoming routes, rearrange the order of options on incoming screen + *2.5.0beta1.1* #2991, #2999, #2993, #3020, #3002, #3041, #2996, #3029, #3031 + *2.5.0beta1.0* #2976, #2778, #2413, #2974, #2962, #2894, #2073 + *2.5.0alpha1.1* #2930, #2938, #2922, #2939, #1568, #2949, #2779, #2953, #1911, #2807 + *2.5.0alpha1.0* #2853, #2925, #2391, #2785, #2923, #2671, #2585, #2908, #2909, #2510, #2976, #2845, #1715, #2760, #2926, #2927, #2838, also added misc icons, links between users/devices + *2.4.0.4* #2857, #2848, #2791, #2449 + *2.4.0.3* #2851 forwarded/followme/ringgroup calls originating from outside fail + *2.4.0.2* #2702, #2759, #2763, #2801, #2747, #2828, #2604, #2841, #2835 - review svn log or tickets for details + *2.4.0.1* #2693 fix DID editing creating new DID route introduced from #2664 fix + *2.4.0.0* CHANGELOG TRUNCATED See SVN Repository + + + 2.5.0rc3 + + + /usr/sbin/asterisk + + + Extensions + Users + Devices + Inbound Routes + Zap Channel DIDs + Outbound Routes + Trunks + General Settings + Administrators + FreePBX Support + + release/2.5/core-2.5.0rc3.0.tgz + 3bdffec1a0c440cc5c4d9c35df40cfda + Index: /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/page.trunks.php =================================================================== --- /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/page.trunks.php (revision 6543) +++ /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/page.trunks.php (revision 6543) @@ -0,0 +1,856 @@ +unserialize($str); + $xmldata = $xml->getUnserializedData(); + + if (isset($xmldata['lca-data']['prefix'])) { + + if ($action == 'populatenpanxx10') { + // 10 digit dialing + // - add area code to 7 digits + // - match local 10 digits + // - add 1 to anything else + $dialrules[] = $matches[1].'NXXXXXX'; + // add NPA to 7-digits + foreach ($xmldata['lca-data']['prefix'] as $prefix) { + $dialrules[] = $prefix['npa'].'+'.$prefix['nxx'].'XXXX'; + } + foreach ($xmldata['lca-data']['prefix'] as $prefix) { + $dialrules[] = $prefix['npa'].$prefix['nxx'].'XXXX'; + } + // if a number was not matched as local, dial it with '1' prefix + $dialrules[] = '1+NXXNXXXXXX'; + } else { + // 7 digit dialing + // - drop area code from local numbers + // - match local 7 digit numbers + // - add 1 to everything else + foreach ($xmldata['lca-data']['prefix'] as $prefix) { + $dialrules[] = $prefix['npa'].'|'.$prefix['nxx'].'XXXX'; + } + foreach ($xmldata['lca-data']['prefix'] as $prefix) { + $dialrules[] = $prefix['nxx'].'XXXX'; + } + $dialrules[] = '1+NXXNXXXXXX'; + $dialrules[] = '1'.$matches[1].'+NXXXXXX'; + } + + // check for duplicates, and re-sequence + $dialrules = array_values(array_unique($dialrules)); + } else { + $errormsg = _("Error fetching prefix list for: "). $_REQUEST["npanxx"]; + } + + } else { + // what a horrible error message... :p + $errormsg = _("Invalid format for NPA-NXX code (must be format: NXXNXX)"); + } + + if (isset($errormsg)) { + echo ""; + unset($errormsg); + } + break; +} + +//get all rows from globals +$sql = "SELECT * FROM globals"; +$globals = $db->getAll($sql); +if(DB::IsError($globals)) { + die_freepbx($globals->getMessage()); +} + +//create a set of variables that match the items in global[0] +foreach ($globals as $global) { + ${trim($global[0])} = htmlentities($global[1]); +} + +?> + + + + +
+ + +

+ $baseURL.'tech=ZAP', 'tlabel' => _("Add Zap Trunk")), + array('url'=> $baseURL.'tech=IAX2', 'tlabel' => _("Add IAX2 Trunk")), + array('url'=> $baseURL.'tech=SIP', 'tlabel' => _("Add SIP Trunk")), + array('url'=> $baseURL.'tech=ENUM', 'tlabel' => _("Add ENUM Trunk")), + array('url'=> $baseURL.'tech=DUNDI', 'tlabel' => _("Add DUNDi Trunk")), + array('url'=> $baseURL.'tech=CUSTOM', 'tlabel' => _("Add Custom Trunk")), + ); + foreach ($trunks as $trunk) { + $label = ' '.$trunk['tlabel'].''; + echo "".$label."

"; + } +} else { + if ($extdisplay) { + //list($trunk_tech, $trunk_name) = explode("/",$tname); + //if ($trunk_tech == "IAX2") $trunk_tech = "IAX"; // same thing + $tech = core_trunks_getTrunkTech($trunknum); + + $outcid = ${"OUTCID_".$trunknum}; + $maxchans = ${"OUTMAXCHANS_".$trunknum}; + $dialoutprefix = ${"OUTPREFIX_".$trunknum}; + $keepcid = isset(${"OUTKEEPCID_".$trunknum})?${"OUTKEEPCID_".$trunknum}:''; + $failtrunk = isset(${"OUTFAIL_".$trunknum})?${"OUTFAIL_".$trunknum}:''; + $failtrunk_enable = ($failtrunk == "")?'':'CHECKED'; + $disabletrunk = isset(${"OUTDISABLE_".$trunknum})?${"OUTDISABLE_".$trunknum}:''; + + + if ($tech!="enum") { + + if (!isset($channelid)) { + $channelid = core_trunks_getTrunkTrunkName($trunknum); + } + + if ($tech!="custom" && $tech!="dundi") { // custom trunks will not have user/peer details in database table + // load from db + if (!isset($peerdetails)) { + $peerdetails = core_trunks_getTrunkPeerDetails($trunknum); + } + + if (!isset($usercontext)) { + $usercontext = core_trunks_getTrunkUserContext($trunknum); + } + + if (!isset($userconfig)) { + $userconfig = core_trunks_getTrunkUserConfig($trunknum); + } + + if (!isset($register)) { + $register = core_trunks_getTrunkRegister($trunknum); + } + } + } + + if (count($dialrules) == 0) { + if ($temp = core_trunks_getDialRules($trunknum)) { + foreach ($temp as $key=>$val) { + // extract all ruleXX keys + if (preg_match("/^rule\d+$/",$key)) { + $dialrules[] = $val; + } + } + } + unset($temp); + } + + echo "

".sprintf(_("Edit %s Trunk"),strtoupper($tech))."

"; + $tlabel = sprintf(_("Delete Trunk %s"),substr($channelid,0,20)); + $label = ' '.$tlabel.''; +?> +

+ 0) { + echo " "._("In use by")." ".$num_routes." ".($num_routes == 1 ? _("route") : _("routes")).""; + foreach($routes as $route=>$priority) { + echo _("Route")." ".$route.": "._("Sequence")." ".$priority."
"; + } + echo "
"; + } else { + echo " "._("WARNING:")." "._("This trunk is not used by any routes!").""; + echo _("This trunk will not be able to be used for outbound calls until a route is setup that uses it. Click on Outbound Routes to setup routing."); + echo ""; + } + + } else { + // set defaults + $outcid = ""; + $maxchans = ""; + $dialoutprefix = ""; + + if ($tech == "zap") { + $channelid = "g0"; + } else { + $channelid = ""; + } + + // only for iax2/sip + $peerdetails = "host=***provider ip address***\nusername=***userid***\nsecret=***password***\ntype=peer"; + $usercontext = ""; + $userconfig = "secret=***password***\ntype=user\ncontext=from-trunk"; + $register = ""; + + $localpattern = "NXXXXXX"; + $lddialprefix = "1"; + $areacode = ""; + + echo "

".sprintf("Add %s Trunk",strtoupper($tech))."

"; + + } +switch ($tech) { + case 'dundi': + $helptext = _('FreePBX offers limited support for DUNDi trunks and additional manual configuration is required. The trunk name should correspond to the [mappings] section of the remote dundi.conf systems. For example, you may have a mapping on the remote system, and corresponding configurations in dundi.conf locally, that looks as follows:

[mappings]
priv => dundi-extens,0,IAX2,priv:${SECRET}@218.23.42.26/${NUMBER},noparital

In this example, you would create this trunk and name it priv. You would then create the corresponding IAX2 trunk with proper settings to work with DUNDi. This can be done by making an IAX2 trunk in FreePBX or by using the iax_custom.conf file.
The dundi-extens context in this example must be created in extensions_custom.conf. This can simply include contexts such as ext-local, ext-intercom-users, ext-paging and so forth to provide access to the corresponding extensions and features provided by these various contexts and generated by FreePBX.'); + break; + default: + $helptext = ''; +} +if ($helptext != '') { + if ($extdisplay) { + echo "

"; + } + echo $helptext; +} + +?> + +
');"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +*/?> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

+
+

Format: \"caller name\" <#######>. You can also use the magic string 'hidden' to hide the CallerID sent out over Digital lines ONLY (E1/T1/J1/BRI/SIP/IAX)")?>

: +
+ +
+


: +
+ tabindex=""/> +
+ + : + + : + + + +
: + + OnClick='disable_verify(disabletrunk); return true;'> +
: + + type="text" size="20" name="failtrunk" value=""/> + OnClick='disable_field(failtrunk,failtrunk_enable); return true;'> +
+

+
+


+ X   
+ Z   
+ N   
+ [1237-9] 
+ .   
+ |    + +   

+ +
: +
+
+ + " tabindex=""/> +
+ +
+
+
+
: +
+
+ Dial rulesThe area code this trunk is in.: +   + + +
+ The dialing pattern to make a 'local' call."): + + + +
+ The prefix for dialing long-distance numbers. In north america, this should be \"1\".")?>: + + + +
+ The area code this trunk is in. Any 7-digit numbers that don't match a number in the below list will have dialprefix+areacode added to them. ")?>: + + + +
+ This should be a list of local areacodes + prefixes to use for local dialing.")?>: +   +
+ +
+ Do a lookup from http://members.dandy.net/~czg/search.html to find all local-reachable area codes and phone numbers.")?>: +

+
+
Most users should leave this option blank.")?>
: +
+ +
+

+
+

The default setting is g0 (group zero).")?>

: +
+ + +
+ $OUTNUM$
examples:

CAPI/XXXXXXXX/")?>$OUTNUM$H323/")?>$OUTNUM$@XX.XX.XX.XX
OH323/$OUTNUM$@XX.XX.XX.XX:XXXX
vpb/1-1/$OUTNUM$
: +
+ + +
+ : + + + +
+


: +
+ +
+

You may need to add to the default lines listed below, depending on your provider.

WARNING: Order is important as it will be retained. For example, if you use the \"allow/deny\" directives make sure deny comes first.")?>

: +
+ +
+

+
+

This USER Context will be used to define the below user details.")?>
: +
+ +
+



WARNING: Order is important as it will be retained. For example, if you use the \"allow/deny\" directives make sure deny + comes first.")?>

: +
+ +
+

+
+

example:

username:password@switch.voipprovider.com.

Many providers will require you to provide a DID number, ex: username:password@switch.voipprovider.com/didnumber in order for any DID matching to work.")?>

: +
+ +
+
" tabindex="">
+
+ + + +
+ + + Index: /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/page.routing.php =================================================================== --- /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/page.routing.php (revision 6521) +++ /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/page.routing.php (revision 6521) @@ -0,0 +1,677 @@ + Copyright (C) 2005 Ron Hartmann (rhartmann@vercomsystems.com) +// Asterisk Management Portal Copyright (C) 2004 Coalescent Systems Inc. (info@coalescentsystems.ca) +// +//This program is free software; you can redistribute it and/or +//modify it under the terms of the GNU General Public License +//as published by the Free Software Foundation; either version 2 +//of the License, or (at your option) any later version. +// +//This program is distributed in the hope that it will be useful, +//but WITHOUT ANY WARRANTY; without even the implied warranty of +//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +//GNU General Public License for more details. + +$display='routing'; +$extdisplay=isset($_REQUEST['extdisplay'])?$_REQUEST['extdisplay']:''; +$action = isset($_REQUEST['action'])?$_REQUEST['action']:''; + +$repotrunkdirection = isset($_REQUEST['repotrunkdirection'])?$_REQUEST['repotrunkdirection']:''; +$repotrunkkey = isset($_REQUEST['repotrunkkey'])?$_REQUEST['repotrunkkey']:''; + + +$dialpattern = array(); +if (isset($_REQUEST["dialpattern"])) { + //$dialpattern = $_REQUEST["dialpattern"]; + $dialpattern = explode("\n",$_REQUEST["dialpattern"]); + + if (!$dialpattern) { + $dialpattern = array(); + } + + foreach (array_keys($dialpattern) as $key) { + //trim it + $dialpattern[$key] = trim($dialpattern[$key]); + + // remove blanks + if ($dialpattern[$key] == "") unset($dialpattern[$key]); + + // remove leading underscores (we do that on backend) + if ($dialpattern[$key][0] == "_") $dialpattern[$key] = substr($dialpattern[$key],1); + } + + // check for duplicates, and re-sequence + $dialpattern = array_values(array_unique($dialpattern)); +} + +if ( (isset($_REQUEST['reporoutedirection'])) && (isset($_REQUEST['reporoutekey']))) { + $routepriority = core_routing_getroutenames(); + $routepriority = core_routing_setroutepriority($routepriority, $_REQUEST['reporoutedirection'], $_REQUEST['reporoutekey']); +} + +$trunkpriority = array(); +if (isset($_REQUEST["trunkpriority"])) { + $trunkpriority = $_REQUEST["trunkpriority"]; + + if (!$trunkpriority) { + $trunkpriority = array(); + } + + // delete blank entries and reorder + foreach (array_keys($trunkpriority) as $key) { + if (empty($trunkpriority[$key])) { + // delete this empty + unset($trunkpriority[$key]); + + } else if (($key==($repotrunkkey-1)) && ($repotrunkdirection=="up")) { + // swap this one with the one before (move up) + $temptrunk = $trunkpriority[$key]; + $trunkpriority[ $key ] = $trunkpriority[ $key+1 ]; + $trunkpriority[ $key+1 ] = $temptrunk; + + } else if (($key==($repotrunkkey)) && ($repotrunkdirection=="down")) { + // swap this one with the one after (move down) + $temptrunk = $trunkpriority[ $key+1 ]; + $trunkpriority[ $key+1 ] = $trunkpriority[ $key ]; + $trunkpriority[ $key ] = $temptrunk; + } + } + unset($temptrunk); + $trunkpriority = array_values($trunkpriority); // resequence our numbers + $trunkpriority = array_unique($trunkpriority); // resequence our numbers +} + +$routename = isset($_REQUEST["routename"]) ? $_REQUEST["routename"] : ""; +$routepass = isset($_REQUEST["routepass"]) ? $_REQUEST["routepass"] : ""; +$emergency = isset($_REQUEST["emergency"]) ? $_REQUEST["emergency"] : ""; +$intracompany = isset($_REQUEST["intracompany"]) ? $_REQUEST["intracompany"] : ""; +$mohsilence = isset($_REQUEST["mohsilence"]) ? $_REQUEST["mohsilence"] : ""; + +//if submitting form, update database +switch ($action) { + case "addroute": + core_routing_add($routename, $dialpattern, $trunkpriority,"new", $routepass, $emergency, $intracompany, $mohsilence); + needreload(); + redirect_standard(); + break; + case "editroute": + core_routing_edit($routename, $dialpattern, $trunkpriority, $routepass, $emergency, $intracompany, $mohsilence); + needreload(); + redirect_standard('extdisplay'); + break; + case "delroute": + core_routing_del($extdisplay); + // re-order the routes to make sure that there are no skipped numbers. + // example if we have 001-test1, 002-test2, and 003-test3 then delete 002-test2 + // we do not want to have our routes as 001-test1, 003-test3 we need to reorder them + // so we are left with 001-test1, 002-test3 + $routepriority = core_routing_getroutenames(); + $routepriority = core_routing_setroutepriority($routepriority, '',''); + needreload(); + redirect_standard(); + break; + case 'renameroute': + if (core_routing_rename($routename, $_REQUEST["newroutename"])) { + needreload(); + } else { + echo ""; + } + $route_prefix=substr($routename,0,4); + $extdisplay=$route_prefix.$_REQUEST["newroutename"]; + + break; + case 'prioritizeroute': + needreload(); + break; + case 'populatenpanxx': + if (preg_match("/^([2-9]\d\d)-?([2-9]\d\d)$/", $_REQUEST["npanxx"], $matches)) { + // first thing we do is grab the exch: + $ch = curl_init(); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_URL, "http://www.localcallingguide.com/xmllocalprefix.php?npa=".$matches[1]."&nxx=".$matches[2]); + curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (Linux; FreePBX Local Trunks Configuration)"); + $str = curl_exec($ch); + curl_close($ch); + + // quick 'n dirty - nabbed from PEAR + require_once($amp_conf['AMPWEBROOT'] . '/admin/modules/core/XML_Parser.php'); + require_once($amp_conf['AMPWEBROOT'] . '/admin/modules/core/XML_Unserializer.php'); + + $xml = new xml_unserializer; + $xml->unserialize($str); + $xmldata = $xml->getUnserializedData(); + + if (isset($xmldata['lca-data']['prefix'])) { + // we do the loops separately so patterns are grouped together + + // match 1+NPA+NXX (dropping 1) + foreach ($xmldata['lca-data']['prefix'] as $prefix) { + $dialpattern[] = '1|'.$prefix['npa'].$prefix['nxx'].'XXXX'; + } + // match NPA+NXX + foreach ($xmldata['lca-data']['prefix'] as $prefix) { + $dialpattern[] = $prefix['npa'].$prefix['nxx'].'XXXX'; + } + // match 7-digits + foreach ($xmldata['lca-data']['prefix'] as $prefix) { + $dialpattern[] = $prefix['nxx'].'XXXX'; + } + + // check for duplicates, and re-sequence + $dialpattern = array_values(array_unique($dialpattern)); + } else { + $errormsg = _("Error fetching prefix list for: "). $_REQUEST["npanxx"]; + } + + } else { + // what a horrible error message... :p + $errormsg = _("Invalid format for NPA-NXX code (must be format: NXXNXX)"); + } + + if (isset($errormsg)) { + echo ""; + unset($errormsg); + } + break; +} + + + +//get all rows from globals +$sql = "SELECT * FROM globals"; +$globals = $db->getAll($sql); +if(DB::IsError($globals)) { +die_freepbx($globals->getMessage()); +} + +//create a set of variables that match the items in global[0] +foreach ($globals as $global) { + ${trim($global[0])} = htmlentities($global[1]); +} + +?> +
+ + + +
+
    +
  • href="config.php?display=">
  • +\n\t\t$key " . substr($tresult[0],4)."\n"; + + if ($key > 0) + echo "\t\t" .  _("Move Up") .
+				"\n"; + else + echo "\t\t\n"; + + // move down + if ($key < ($positions-1)) + echo "\t\t','down')\" alt='" . _("Move Down") . + "' style='float:none; margin-left:0px; margin-bottom:0px;' width='9' height='11'>\n"; + else + echo "\t\t\n"; + echo "\t\n"; +} // foreach +?> +
+
+ +
+ +"._("Edit Route").""; +} else { + echo "

"._("Add Route")."

"; +} + +// build trunks associative array +foreach (core_trunks_list() as $temp) { + $trunks[$temp[0]] = $temp[1]; + $trunkstate[$temp[0]] = $temp[2]; +} + +if ($extdisplay) { // editing + $tlabel = sprintf(_("Delete Route %s"),substr($extdisplay,4)); + $label = ' '.$tlabel.''; +?> +

+ + +
');"> + + + + + + + + + + + + + + + + +hookHtml; +?> + + + + + + + + + + + + + + + + + +$pattern) { +?> + + + + + + + + + + + + + + + + + + + + + + + + + +$trunk) { +?> + + + + + 3) ? 3 : count($trunks))); + +for ($i=0; $i < $num_new_boxes; $i++) { +?> + + + + + + + + + + + + + + + +
+


: +
+ + + " style="font-size:10px;" /> + + + + +
:
A numerical password, or the path to an Authenticate password file can be used.

Leave this field blank to not prompt for password.
")?>
")?>: tabindex=""/>
")?>: tabindex=""/>
+ +
+


+ X   
+ Z   
+ N   
+ [1237-9] 
+ .   
+ |    +
+
+ + +
+ + +
  +
"> +
+ +
+ + " /> +
+ +
+
+
: + + +
+ +
+
For Dial Patterns that match long distance numbers, for example, you'd want to pick the cheapest routes for long distance (ie, VoIP trunks first) followed by more expensive routes (POTS lines).")?>
+
   + + + + + 0) {?> + <?php echo _(" style="float:none; margin-left:0px; margin-bottom:0px;" width="9" height="11"> + + + + <?php echo _(" style="float:none; margin-left:0px; margin-bottom:0px;" width="9" height="11"> + + + +
  + +
+ "> +
+
"> +
+
+ + + +
+ Index: /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/page.zapchandids.php =================================================================== --- /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/page.zapchandids.php (revision 5919) +++ /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/page.zapchandids.php (revision 5919) @@ -0,0 +1,157 @@ + +
+ +
+ +
+ +"._("Edit Zap Channel: ").$channel.""; +} else { + echo "

"._("Add Zap Channel")."

"; +} + +$helptext = _("Zap Channel DIDs allow you to assign a DID to specific Zap Channels. You can supply the same DID to multiple channels. This would be a common scenario if you have multiple POTS lines that are on a hunt group from your provider. You MUST assign the channel's context to from-zaptel for these settings to have effect. It will be a line that looks like:

context = from-zaptel

in your zapata.conf configuration effecting the specified channel(s). Once you have assigned DIDs you can use standard Inbound Routes with the specified DIDs to route your calls."); +echo "

".$helptext."

\n"; +?> +
+ + + + + + + + + + + + + + + + + + + + + + + + +

:
:
:

" tabindex=""> + '; } ?> +
+
+ + + Index: /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/install.php =================================================================== --- /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/install.php (revision 6527) +++ /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/install.php (revision 6527) @@ -0,0 +1,243 @@ +"; + } +} + +if (! function_exists("outn")) { + function outn($text) { + echo $text; + } +} + +function did_migrate($incoming){ + global $db; + + foreach ($incoming as $key => $val) { + ${$key} = $db->escapeSimple($val); + } + + // Check to make sure the did is not being used elsewhere + // + $sql = "SELECT * FROM incoming WHERE cidnum = '' AND extension = '$extension'"; + $existing = $db->getAll($sql, DB_FETCHMODE_ASSOC); + if(DB::IsError($existing)) { + outn(sprintf(_("ERROR: trying to check if %s already in use"),$extension)); + return false; + } + if (empty($existing)) { + $sql="INSERT INTO incoming (cidnum,extension,destination,faxexten,faxemail,answer,wait,privacyman,alertinfo, ringing, mohclass, description, grppre) values ('$cidnum','$extension','$destination','$faxexten','$faxemail','$answer','$wait','$privacyman','$alertinfo', '$ringing', '$mohclass', '$description', '$grppre')"; + sql($sql); + return true; + } else { + return false; + } +} + +$fcc = new featurecode('core', 'userlogon'); +$fcc->setDescription('User Logon'); +$fcc->setDefault('*11'); +$fcc->update(); +unset($fcc); + +$fcc = new featurecode('core', 'userlogoff'); +$fcc->setDescription('User Logoff'); +$fcc->setDefault('*12'); +$fcc->update(); +unset($fcc); + +$fcc = new featurecode('core', 'zapbarge'); +$fcc->setDescription('ZapBarge'); +$fcc->setDefault('888'); +$fcc->update(); +unset($fcc); + +$fcc = new featurecode('core', 'chanspy'); +$fcc->setDescription('ChanSpy'); +$fcc->setDefault('555'); +$fcc->update(); +unset($fcc); + +$fcc = new featurecode('core', 'simu_pstn'); +$fcc->setDescription('Simulate Incoming Call'); +$fcc->setDefault('7777'); +$fcc->update(); +unset($fcc); + +$fcc = new featurecode('core', 'simu_fax'); +$fcc->setDescription('Dial System FAX'); +$fcc->setDefault('666'); +$fcc->update(); +unset($fcc); + +$fcc = new featurecode('core', 'pickup'); +$fcc->setDescription('Directed Call Pickup'); +$fcc->setDefault('**'); +$fcc->update(); +unset($fcc); + +$fcc = new featurecode('core', 'pickupexten'); +$fcc->setDescription('Asterisk General Call Pickup'); +$fcc->setDefault('*8'); +$fcc->update(); +unset($fcc); + +$fcc = new featurecode('core', 'blindxfer'); +$fcc->setDescription('In-Call Asterisk Blind Transfer'); +$fcc->setDefault('##'); +$fcc->update(); +unset($fcc); + +$fcc = new featurecode('core', 'atxfer'); +$fcc->setDescription('In-Call Asterisk Attended Transfer'); +$fcc->setDefault('*2'); +$fcc->update(); +unset($fcc); + +$fcc = new featurecode('core', 'automon'); +$fcc->setDescription('In-Call Asterisk Toggle Call Recording'); +$fcc->setDefault('*1'); +$fcc->update(); +unset($fcc); + +$fcc = new featurecode('core', 'disconnect'); +$fcc->setDescription('In Call Disconnect Code'); +$fcc->setDefault('**'); +$fcc->update(); +unset($fcc); + + +// Version 2.5 Upgrade needs to migrate directdid user info to incoming table +// +outn(_("Checking if directdids need migrating..")); +$sql = "SELECT `directdid` FROM `users`"; +$check = $db->getRow($sql, DB_FETCHMODE_ASSOC); +if(!DB::IsError($check)) { + out(_("starting migration")); + $errors = 0; + $sql = "SELECT * FROM `users` WHERE `directdid` != '' AND `directdid` IS NOT NULL"; + $direct_dids_arr = $db->getAll($sql, DB_FETCHMODE_ASSOC); + if(!DB::IsError($direct_dids_arr)) { + foreach ($direct_dids_arr as $direct_dids) { + $did_vars['destination'] = 'from-did-direct,'.$direct_dids['extension'].',1'; + $did_vars['extension'] = $direct_dids['directdid']; + $did_vars['cidnum'] = ''; + $did_vars['faxexten'] = $direct_dids['faxexten']; + $did_vars['faxemail'] = $direct_dids['faxemail']; + $did_vars['answer'] = $direct_dids['answer']; + $did_vars['wait'] = $direct_dids['wait']; + $did_vars['privacyman'] = $direct_dids['privacyman']; + $did_vars['alertinfo'] = $direct_dids['didalert']; + $did_vars['ringing'] = ''; + $did_vars['mohclass'] = $direct_dids['mohclass']; + $did_vars['description'] = _("User: ").$direct_dids['extension']; + $did_vars['grppre'] = ''; + if (!did_migrate($did_vars)) { + out(sprintf(_("ERROR: failed to insert %s for user %s"),$direct_dids['directdid'],$direct_dids['extension'])); + $errors++; + } + } + if ($errors) { + out(sprintf(_("There were %s failures migrating directdids, users table not being changed"),$errors)); + } else { + $migrate_array = array('directdid', 'didalert', 'mohclass', 'faxexten', 'faxemail', 'answer', 'wait', 'privacyman'); + foreach ($migrate_array as $field) { + outn(sprintf(_("Removing field %s from users table.."),$field)); + $sql = "ALTER TABLE `users` DROP `".$field."`"; + $results = $db->query($sql); + if (DB::IsError($results)) { + out(_("not present")); + } else { + out(_("removed")); + } + } + } + } else { + out(_("ERROR: could not access user table to migrate directdids to incoming table, aborting")); + } +} else { + out(_("already done")); +} + +// Add callgroup, pickupgroup to zap + +outn(_("updating zap callgroup, pickupgroup..")); +$sql = "SELECT `id` FROM `devices` WHERE `tech` = 'zap'"; +$results = $db->getCol($sql); +if(DB::IsError($results)) { + $results = null; +} +$count_pickup = 0; +$count_callgroup = 0; +if (isset($results) && !empty($results)) { + foreach ($results as $device) { + // if the insert fails then it is already there since it will violate the primary key but that is ok + // + $sql = "INSERT INTO `zap` (`id`, `keyword`, `data`, `flags`) VALUES ('$device', 'callgroup', '', '0')"; + $try = $db->query($sql); + if(!DB::IsError($try)) { + $count_pickup++; + } + $sql = "INSERT INTO `zap` (`id`, `keyword`, `data`, `flags`) VALUES ('$device', 'pickupgroup', '', '0')"; + $try = $db->query($sql); + if(!DB::IsError($try)) { + $count_callgroup++; + } + } +} +if ($count_callgroup || $count_pickup) { + out(sprintf(_("updated %s callgroups, %s pickupgroups"),$count_callgroup,$count_pickup)); +} else { + out(_("not needed")); +} + +// 2.5 new field +// +outn(_("checking for delay_answer field ..")); +$sql = "SELECT `delay_answer` FROM `incoming`"; +$check = $db->getRow($sql, DB_FETCHMODE_ASSOC); +if(DB::IsError($check)) { + $sql = "ALTER TABLE `incoming` ADD `delay_answer` INT(2) DEFAULT NULL"; + $result = $db->query($sql); + if(DB::IsError($result)) { + out(_("fatal error")); + die_freepbx($result->getDebugInfo()); + } else { + out(_("added")); + } +} else { + out(_("already exists")); +} + +outn(_("checking for pricid field ..")); +$sql = "SELECT `pricid` FROM `incoming`"; +$check = $db->getRow($sql, DB_FETCHMODE_ASSOC); +if(DB::IsError($check)) { + $sql = "ALTER TABLE `incoming` ADD `pricid` VARCHAR(20) DEFAULT NULL"; + $result = $db->query($sql); + if(DB::IsError($result)) { + out(_("fatal error")); + die_freepbx($result->getDebugInfo()); + } else { + out(_("added")); + } +} else { + out(_("already exists")); +} + +// Add variable RECORDING_STATE use to globally disable recording +// TODO: move this to the upgrade script so it only has to be done +// once in a major upgrade when we move to next major release +// +outn("Checking for Global var RECORDING_STATE.."); +$nrows = $db->getOne("SELECT count(*) from globals where variable='RECORDING_STATE'"); +if (!$nrows) { + $db->query("insert into globals values ('RECORDING_STATE', 'ENABLED')"); + out("Created"); +} else { + out("Already exists!"); +} + +?> Index: /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/page.ampusers.php =================================================================== --- /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/page.ampusers.php (revision 5953) +++ /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/page.ampusers.php (revision 5953) @@ -0,0 +1,271 @@ + $module) { + //create an array of module sections to display + if (isset($module['items']) && is_array($module['items'])) { + foreach($module['items'] as $itemKey => $item) { + $listKey = (!empty($item['display']) ? $item['display'] : $itemKey); + $module_list[ $listKey ] = $item; + } + } + } +} + +// extensions vs device/users ... module_list setting +if (isset($amp_conf["AMPEXTENSIONS"]) && ($amp_conf["AMPEXTENSIONS"] == "deviceanduser")) { + unset($module_list["extensions"]); +} else { + unset($module_list["devices"]); + unset($module_list["users"]); +} + +// no more adding the APPLY Changes bar to module list because array_multisort messes up integer array keys +// $module_list['99'] = array('category' => NULL, 'name' => _("Apply Changes Bar")); + +// changed from $module_name to $admin_module_name because the former is used by framework +foreach ($module_list as $key => $row) { + $module_category[$key] = $row['category']; + $admin_module_name[$key] = $row['name']; +} +array_multisort($module_category, SORT_ASC, $admin_module_name, SORT_ASC, $module_list); + +$sections = array(); +if (isset($_REQUEST["sections"])) { + if (is_array($_REQUEST["sections"])) { + $sections = $_REQUEST["sections"]; + } else { + //TODO do we even need this?? + $sections = explode(";",$_REQUEST["sections"]); + } +} + +//if submitting form, update database +switch ($action) { + case "addampuser": + core_ampusers_add($username, $password, $extension_low, $extension_high, $deptname, $sections); + //indicate 'need reload' link in footer.php + needreload(); + redirect_standard(); + break; + case "editampuser": + core_ampusers_del($userdisplay); + core_ampusers_add($username, $password, $extension_low, $extension_high, $deptname, $sections); + //indicate 'need reload' link in footer.php + needreload(); + redirect_standard('userdisplay'); + break; + case "delampuser": + core_ampusers_del($userdisplay); + //indicate 'need reload' link in footer.php + needreload(); + $userdisplay = ""; // go "add" screen + redirect_standard(); + break; +} + +?> +
+ + + +
+ +"._("Edit Administrator").""; + + $user = getAmpUser($userdisplay); + + $username = $user["username"]; + $password = $user["password"]; + $extension_high = $user["extension_high"]; + $extension_low = $user["extension_low"]; + $deptname = $user["deptname"]; + $sections = $user["sections"]; + + $tlabel = sprintf(_("Delete User: %s"),$userdisplay); + $label = ' '.$tlabel.''; +?> +

+"._("Add Administrator").""; + } +?> + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

+
+ '._("NOTE:").''._("AUTHTYPE is not set to 'database' in /etc/amportal.conf - note that this module is not currently providing access control, and changing passwords here or adding users will have no effect unless AUTHTYPE is set to 'database'.") ?>

+
+ Create a unique username for this new user")?>: + + +
+ Create a password for this new user")?>: + + +
+
+

+
+ Restrict this user's view of Digital Receptionist menus and System Recordings to only those for this department.")?>: + + +
+ Restrict this user's view to only Extensions, Ring Groups, and Queues within this range.")?>: + + +  to + +
+ Select the Admin Sections this user should have access to.")?>: + + +
+
" onclick="checkAmpUser(ampuserEdit, '')" tabindex="">
+
+
+ + + Index: /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/page.extensions.php =================================================================== --- /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/page.extensions.php (revision 2842) +++ /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/page.extensions.php (revision 2842) @@ -0,0 +1,21 @@ + + +
+ +
+
Index: /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/page.did.php =================================================================== --- /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/page.did.php (revision 6536) +++ /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/page.did.php (revision 6536) @@ -0,0 +1,377 @@ + +
+ + +
+
    +
  • href="config.php?display=">
  • +
  • href="config.php?display=">
  • +
  • href="config.php?display=">
  • +
  • href="config.php?display=">
  • +
  • href="config.php?display=">

  • + $did_items) { + $did_dest = split(',',$did_items['destination']); + if (!isset($did_dest[0]) || $did_dest[0] != 'from-did-direct') { + unset($inroutes[$key]); + } + } + break; + case 'incoming': + foreach ($inroutes as $key => $did_items) { + $did_dest = split(',',$did_items['destination']); + if (!isset($did_dest[0]) || $did_dest[0] == 'from-did-direct') { + unset($inroutes[$key]); + } + } + break; + case 'unassigned': + foreach ($inroutes as $key => $did_items) { + if (isset($did_items['destination']) && $did_items['destination'] != '') { + unset($inroutes[$key]); + } + } + break; + default: +} +if (isset($inroutes)) { + foreach ($inroutes as $inroute) { + $displaydid = ( empty($inroute['extension'])? _("any DID") : $inroute['extension'] ); + $displaycid = ( empty($inroute['cidnum'])? _("any CID") : $inroute['cidnum'] ); + $desc = ( empty($inroute['description'])? "" : $inroute['description']."
    " ); + echo "\t
  • {$desc} {$displaydid} / {$displaycid}
  • \n"; + } +} +?> +
+
+ +
+

Route '.$extdisplay.' '._("deleted").'!









'; + } else { +?> + +

:

+ '.$tlabel.''; + echo "

".$label."

"; + // If this is a direct did, e.g. from-did-direct,nnn,1 then make a link to the extension + // + $did_dest = split(',',$destination); + if (isset($did_dest[0]) && $did_dest[0] == 'from-did-direct') { + + if (isset($amp_conf["AMPEXTENSIONS"]) && ($amp_conf["AMPEXTENSIONS"] == "deviceanduser")) { + $editURL = $_SERVER['PHP_SELF'].'?display=users&extdisplay='.$did_dest[1]; + $EXTorUSER = _("User"); + } + else { + $editURL = $_SERVER['PHP_SELF'].'?display=extensions&extdisplay='.$did_dest[1]; + $EXTorUSER = _("Extension"); + } + $result = core_users_get($did_dest[1]); + $label = ' '.sprintf(_("Edit %s %s (%s)"),$EXTorUSER, $did_dest[1],$result['name']).''; + echo "

".$label."

"; + } +?> + +

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +hookHtml; +?> + + + + + + + + +

:

Leave this blank to match calls with any or no DID info.

You can also use a pattern match (eg _2[345]X) to match a range of numbers')?>
:

Leave this field blank to match any or no CID info. In addition to standard dial sequences, you can also put Private, Blocked, Unknown, Restricted, Anonymous and Unavailable in order to catch these special cases if the Telco transmits them.')?>
:
: tabindex=""/>

:
:
: + +
: tabindex=""/>
:

: + +

+
The FreePBX default is defined in General Settings.")?>
: +
+ +
+
Leave this blank to use the FreePBX default in General Settings.")?>
: +
+ +
: + +
:



+
" tabindex="">   + " >
+
+ +
+ Index: /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/page.users.php =================================================================== --- /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/page.users.php (revision 2617) +++ /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/page.users.php (revision 2617) @@ -0,0 +1,48 @@ + + +
+ +
+ + + + Index: /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/XML_Parser.php =================================================================== --- /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/XML_Parser.php (revision 2070) +++ /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/XML_Parser.php (revision 2070) @@ -0,0 +1,685 @@ + | +// | Tomas V.V.Cox | +// | Stephan Schmidt | +// +----------------------------------------------------------------------+ +// +// $Id: Parser.php,v 1.26 2005/09/23 11:51:10 schst Exp $ + +/** + * XML Parser class. + * + * This is an XML parser based on PHP's "xml" extension, + * based on the bundled expat library. + * + * @category XML + * @package XML_Parser + * @author Stig Bakken + * @author Tomas V.V.Cox + * @author Stephan Schmidt + */ + +/** + * uses PEAR's error handling + */ +//require_once 'PEAR.php'; + +/** + * resource could not be created + */ +define('XML_PARSER_ERROR_NO_RESOURCE', 200); + +/** + * unsupported mode + */ +define('XML_PARSER_ERROR_UNSUPPORTED_MODE', 201); + +/** + * invalid encoding was given + */ +define('XML_PARSER_ERROR_INVALID_ENCODING', 202); + +/** + * specified file could not be read + */ +define('XML_PARSER_ERROR_FILE_NOT_READABLE', 203); + +/** + * invalid input + */ +define('XML_PARSER_ERROR_INVALID_INPUT', 204); + +/** + * remote file cannot be retrieved in safe mode + */ +define('XML_PARSER_ERROR_REMOTE', 205); + +/** + * XML Parser class. + * + * This is an XML parser based on PHP's "xml" extension, + * based on the bundled expat library. + * + * Notes: + * - It requires PHP 4.0.4pl1 or greater + * - From revision 1.17, the function names used by the 'func' mode + * are in the format "xmltag_$elem", for example: use "xmltag_name" + * to handle the tags of your xml file. + * + * @category XML + * @package XML_Parser + * @author Stig Bakken + * @author Tomas V.V.Cox + * @author Stephan Schmidt + * @todo create XML_Parser_Namespace to parse documents with namespaces + * @todo create XML_Parser_Pull + * @todo Tests that need to be made: + * - mixing character encodings + * - a test using all expat handlers + * - options (folding, output charset) + * - different parsing modes + */ +class XML_Parser extends PEAR +{ + // {{{ properties + + /** + * XML parser handle + * + * @var resource + * @see xml_parser_create() + */ + var $parser; + + /** + * File handle if parsing from a file + * + * @var resource + */ + var $fp; + + /** + * Whether to do case folding + * + * If set to true, all tag and attribute names will + * be converted to UPPER CASE. + * + * @var boolean + */ + var $folding = true; + + /** + * Mode of operation, one of "event" or "func" + * + * @var string + */ + var $mode; + + /** + * Mapping from expat handler function to class method. + * + * @var array + */ + var $handler = array( + 'character_data_handler' => 'cdataHandler', + 'default_handler' => 'defaultHandler', + 'processing_instruction_handler' => 'piHandler', + 'unparsed_entity_decl_handler' => 'unparsedHandler', + 'notation_decl_handler' => 'notationHandler', + 'external_entity_ref_handler' => 'entityrefHandler' + ); + + /** + * source encoding + * + * @var string + */ + var $srcenc; + + /** + * target encoding + * + * @var string + */ + var $tgtenc; + + /** + * handler object + * + * @var object + */ + var $_handlerObj; + + // }}} + // {{{ constructor + + /** + * Creates an XML parser. + * + * This is needed for PHP4 compatibility, it will + * call the constructor, when a new instance is created. + * + * @param string $srcenc source charset encoding, use NULL (default) to use + * whatever the document specifies + * @param string $mode how this parser object should work, "event" for + * startelement/endelement-type events, "func" + * to have it call functions named after elements + * @param string $tgenc a valid target encoding + */ + function XML_Parser($srcenc = null, $mode = 'event', $tgtenc = null) + { + XML_Parser::__construct($srcenc, $mode, $tgtenc); + } + // }}} + + /** + * PHP5 constructor + * + * @param string $srcenc source charset encoding, use NULL (default) to use + * whatever the document specifies + * @param string $mode how this parser object should work, "event" for + * startelement/endelement-type events, "func" + * to have it call functions named after elements + * @param string $tgenc a valid target encoding + */ + function __construct($srcenc = null, $mode = 'event', $tgtenc = null) + { + $this->PEAR('XML_Parser_Error'); + + $this->mode = $mode; + $this->srcenc = $srcenc; + $this->tgtenc = $tgtenc; + } + // }}} + + /** + * Sets the mode of the parser. + * + * Possible modes are: + * - func + * - event + * + * You can set the mode using the second parameter + * in the constructor. + * + * This method is only needed, when switching to a new + * mode at a later point. + * + * @access public + * @param string mode, either 'func' or 'event' + * @return boolean|object true on success, PEAR_Error otherwise + */ + function setMode($mode) + { + if ($mode != 'func' && $mode != 'event') { + $this->raiseError('Unsupported mode given', XML_PARSER_ERROR_UNSUPPORTED_MODE); + } + + $this->mode = $mode; + return true; + } + + /** + * Sets the object, that will handle the XML events + * + * This allows you to create a handler object independent of the + * parser object that you are using and easily switch the underlying + * parser. + * + * If no object will be set, XML_Parser assumes that you + * extend this class and handle the events in $this. + * + * @access public + * @param object object to handle the events + * @return boolean will always return true + * @since v1.2.0beta3 + */ + function setHandlerObj(&$obj) + { + $this->_handlerObj = &$obj; + return true; + } + + /** + * Init the element handlers + * + * @access private + */ + function _initHandlers() + { + if (!is_resource($this->parser)) { + return false; + } + + if (!is_object($this->_handlerObj)) { + $this->_handlerObj = &$this; + } + switch ($this->mode) { + + case 'func': + xml_set_object($this->parser, $this->_handlerObj); + xml_set_element_handler($this->parser, array(&$this, 'funcStartHandler'), array(&$this, 'funcEndHandler')); + break; + + case 'event': + xml_set_object($this->parser, $this->_handlerObj); + xml_set_element_handler($this->parser, 'startHandler', 'endHandler'); + break; + default: + return $this->raiseError('Unsupported mode given', XML_PARSER_ERROR_UNSUPPORTED_MODE); + break; + } + + + /** + * set additional handlers for character data, entities, etc. + */ + foreach ($this->handler as $xml_func => $method) { + if (method_exists($this->_handlerObj, $method)) { + $xml_func = 'xml_set_' . $xml_func; + $xml_func($this->parser, $method); + } + } + } + + // {{{ _create() + + /** + * create the XML parser resource + * + * Has been moved from the constructor to avoid + * problems with object references. + * + * Furthermore it allows us returning an error + * if something fails. + * + * @access private + * @return boolean|object true on success, PEAR_Error otherwise + * + * @see xml_parser_create + */ + function _create() + { + if ($this->srcenc === null) { + $xp = @xml_parser_create(); + } else { + $xp = @xml_parser_create($this->srcenc); + } + if (is_resource($xp)) { + if ($this->tgtenc !== null) { + if (!@xml_parser_set_option($xp, XML_OPTION_TARGET_ENCODING, + $this->tgtenc)) { + return $this->raiseError('invalid target encoding', XML_PARSER_ERROR_INVALID_ENCODING); + } + } + $this->parser = $xp; + $result = $this->_initHandlers($this->mode); + if ($this->isError($result)) { + return $result; + } + xml_parser_set_option($xp, XML_OPTION_CASE_FOLDING, $this->folding); + + return true; + } + return $this->raiseError('Unable to create XML parser resource.', XML_PARSER_ERROR_NO_RESOURCE); + } + + // }}} + // {{{ reset() + + /** + * Reset the parser. + * + * This allows you to use one parser instance + * to parse multiple XML documents. + * + * @access public + * @return boolean|object true on success, PEAR_Error otherwise + */ + function reset() + { + $result = $this->_create(); + if ($this->isError( $result )) { + return $result; + } + return true; + } + + // }}} + // {{{ setInputFile() + + /** + * Sets the input xml file to be parsed + * + * @param string Filename (full path) + * @return resource fopen handle of the given file + * @throws XML_Parser_Error + * @see setInput(), setInputString(), parse() + * @access public + */ + function setInputFile($file) + { + /** + * check, if file is a remote file + */ + if (eregi('^(http|ftp)://', substr($file, 0, 10))) { + if (!ini_get('allow_url_fopen')) { + return $this->raiseError('Remote files cannot be parsed, as safe mode is enabled.', XML_PARSER_ERROR_REMOTE); + } + } + + $fp = @fopen($file, 'rb'); + if (is_resource($fp)) { + $this->fp = $fp; + return $fp; + } + return $this->raiseError('File could not be opened.', XML_PARSER_ERROR_FILE_NOT_READABLE); + } + + // }}} + // {{{ setInputString() + + /** + * XML_Parser::setInputString() + * + * Sets the xml input from a string + * + * @param string $data a string containing the XML document + * @return null + **/ + function setInputString($data) + { + $this->fp = $data; + return null; + } + + // }}} + // {{{ setInput() + + /** + * Sets the file handle to use with parse(). + * + * You should use setInputFile() or setInputString() if you + * pass a string + * + * @param mixed $fp Can be either a resource returned from fopen(), + * a URL, a local filename or a string. + * @access public + * @see parse() + * @uses setInputString(), setInputFile() + */ + function setInput($fp) + { + if (is_resource($fp)) { + $this->fp = $fp; + return true; + } + // see if it's an absolute URL (has a scheme at the beginning) + elseif (eregi('^[a-z]+://', substr($fp, 0, 10))) { + return $this->setInputFile($fp); + } + // see if it's a local file + elseif (file_exists($fp)) { + return $this->setInputFile($fp); + } + // it must be a string + else { + $this->fp = $fp; + return true; + } + + return $this->raiseError('Illegal input format', XML_PARSER_ERROR_INVALID_INPUT); + } + + // }}} + // {{{ parse() + + /** + * Central parsing function. + * + * @return true|object PEAR error returns true on success, or a PEAR_Error otherwise + * @access public + */ + function parse() + { + /** + * reset the parser + */ + $result = $this->reset(); + if ($this->isError($result)) { + return $result; + } + // if $this->fp was fopened previously + if (is_resource($this->fp)) { + + while ($data = fread($this->fp, 4096)) { + if (!$this->_parseString($data, feof($this->fp))) { + $error = &$this->raiseError(); + $this->free(); + return $error; + } + } + // otherwise, $this->fp must be a string + } else { + if (!$this->_parseString($this->fp, true)) { + $error = &$this->raiseError(); + $this->free(); + return $error; + } + } + $this->free(); + + return true; + } + + /** + * XML_Parser::_parseString() + * + * @param string $data + * @param boolean $eof + * @return bool + * @access private + * @see parseString() + **/ + function _parseString($data, $eof = false) + { + return xml_parse($this->parser, $data, $eof); + } + + // }}} + // {{{ parseString() + + /** + * XML_Parser::parseString() + * + * Parses a string. + * + * @param string $data XML data + * @param boolean $eof If set and TRUE, data is the last piece of data sent in this parser + * @throws XML_Parser_Error + * @return Pear Error|true true on success or a PEAR Error + * @see _parseString() + */ + function parseString($data, $eof = false) + { + if (!isset($this->parser) || !is_resource($this->parser)) { + $this->reset(); + } + + if (!$this->_parseString($data, $eof)) { + $error = &$this->raiseError(); + $this->free(); + return $error; + } + + if ($eof === true) { + $this->free(); + } + return true; + } + + /** + * XML_Parser::free() + * + * Free the internal resources associated with the parser + * + * @return null + **/ + function free() + { + if (isset($this->parser) && is_resource($this->parser)) { + xml_parser_free($this->parser); + unset( $this->parser ); + } + if (isset($this->fp) && is_resource($this->fp)) { + fclose($this->fp); + } + unset($this->fp); + return null; + } + + /** + * XML_Parser::raiseError() + * + * Throws a XML_Parser_Error + * + * @param string $msg the error message + * @param integer $ecode the error message code + * @return XML_Parser_Error + **/ + function raiseError($msg = null, $ecode = 0) + { + $msg = !is_null($msg) ? $msg : $this->parser; + $err = &new XML_Parser_Error($msg, $ecode); + return parent::raiseError($err); + } + + // }}} + // {{{ funcStartHandler() + + function funcStartHandler($xp, $elem, $attribs) + { + $func = 'xmltag_' . $elem; + if (strchr($func, '.')) { + $func = str_replace('.', '_', $func); + } + if (method_exists($this->_handlerObj, $func)) { + call_user_func(array(&$this->_handlerObj, $func), $xp, $elem, $attribs); + } elseif (method_exists($this->_handlerObj, 'xmltag')) { + call_user_func(array(&$this->_handlerObj, 'xmltag'), $xp, $elem, $attribs); + } + } + + // }}} + // {{{ funcEndHandler() + + function funcEndHandler($xp, $elem) + { + $func = 'xmltag_' . $elem . '_'; + if (strchr($func, '.')) { + $func = str_replace('.', '_', $func); + } + if (method_exists($this->_handlerObj, $func)) { + call_user_func(array(&$this->_handlerObj, $func), $xp, $elem); + } elseif (method_exists($this->_handlerObj, 'xmltag_')) { + call_user_func(array(&$this->_handlerObj, 'xmltag_'), $xp, $elem); + } + } + + // }}} + // {{{ startHandler() + + /** + * + * @abstract + */ + function startHandler($xp, $elem, &$attribs) + { + return NULL; + } + + // }}} + // {{{ endHandler() + + /** + * + * @abstract + */ + function endHandler($xp, $elem) + { + return NULL; + } + + + // }}}me +} + +/** + * error class, replaces PEAR_Error + * + * An instance of this class will be returned + * if an error occurs inside XML_Parser. + * + * There are three advantages over using the standard PEAR_Error: + * - All messages will be prefixed + * - check for XML_Parser error, using is_a( $error, 'XML_Parser_Error' ) + * - messages can be generated from the xml_parser resource + * + * @package XML_Parser + * @access public + * @see PEAR_Error + */ +class XML_Parser_Error extends PEAR_Error +{ + // {{{ properties + + /** + * prefix for all messages + * + * @var string + */ + var $error_message_prefix = 'XML_Parser: '; + + // }}} + // {{{ constructor() + /** + * construct a new error instance + * + * You may either pass a message or an xml_parser resource as first + * parameter. If a resource has been passed, the last error that + * happened will be retrieved and returned. + * + * @access public + * @param string|resource message or parser resource + * @param integer error code + * @param integer error handling + * @param integer error level + */ + function XML_Parser_Error($msgorparser = 'unknown error', $code = 0, $mode = PEAR_ERROR_RETURN, $level = E_USER_NOTICE) + { + if (is_resource($msgorparser)) { + $code = xml_get_error_code($msgorparser); + $msgorparser = sprintf('%s at XML input line %d:%d', + xml_error_string($code), + xml_get_current_line_number($msgorparser), + xml_get_current_column_number($msgorparser)); + } + $this->PEAR_Error($msgorparser, $code, $mode, $level); + } + // }}} +} +?> Index: /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/agi-bin/fixlocalprefix =================================================================== --- /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/agi-bin/fixlocalprefix (revision 6187) +++ /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/agi-bin/fixlocalprefix (revision 6187) @@ -0,0 +1,253 @@ +#!/usr/bin/php -q +get_variable( $value ); + + if ($r['result'] == 1) + { + $result = $r['data']; + return $result; + } + else + return ''; +} + +function parse_conf($filename, &$conf, &$section) { + if (is_null($conf)) { + $conf = array(); + } + if (is_null($section)) { + $section = "general"; + } + + if (file_exists($filename)) { + $fd = fopen($filename, "r"); + while ($line = fgets($fd, 1024)) { + if (preg_match("/^\s*([a-zA-Z0-9-_]+)\s*=\s*(.*?)\s*([;#].*)?$/",$line,$matches)) { + // name = value + // option line + $conf[$section][ $matches[1] ] = $matches[2]; + } else if (preg_match("/^\s*\[(.+)\]/",$line,$matches)) { + // section name + $section = strtolower($matches[1]); + } 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_conf($filename, $conf, $section); + } + } + } +} + +function sanitizeNumber($number) { + global $agi; + if (strpos($number,"-") !== false) { + $agi->verbose("Stripping hyphens"); + $number = str_replace("-","",$number); + } + return $number; +} + +function fixNumber($pattern, $number, &$agi) { + // valid chars in a pattern are 0-9XNZwW#*\.\[\]\-\+\| + $chars = '0-9XNZwW#*\.\[\]\-'; //escaped pcre-ready + + // convert x n and z to uppercase + $regex = str_replace(array('x','n','z'), array('X','N','Z'), $pattern); + // sanitize the pattern - remove any non-pattern chars + $regex = preg_replace("/[^0-9XNZwW#*\.\[\]\-\+\|]/", "", $regex); + // Also kill the '-' characters outside of groups + $regex = preg_replace("/((?:\[[^\]]*\])*)([^\[\]\-]*)-?/", "$1$2", $regex); + + $agi->verbose('Using pattern '.$regex, 4); + // attempt to grab the pieces of the pattern + if (preg_match('/^(([0-9XNZwW#*\.\[\]\-]+)\|)?(([0-9XNZwW#*\.\[\]\-]+)\+)?([0-9XNZwW#*\.\[\]\-]*)$/', $regex, $matches)) { + // one of NXXXXXX, 613|NXXXXXX 1+NXXXXXX 613|1+NXXXXXX, + // matches[2] = drop (eg 613), matches[4] = prefix (eg 1), matches[5] = rest of number (eg NXXXXX) + + $drop = $matches[2]; + $prefix = $matches[4]; + $static = $matches[5]; + } else if (preg_match('/^(([0-9XNZwW#*\.\[\]\-]+)\+)?(([0-9XNZwW#*\.\[\]\-]+)\|)?([0-9XNZwW#*\.\[\]\-]*)$/', $regex, $matches)) { + // one of NXXXXXX, 613|NXXXXXX 1+NXXXXXX 1+613|NXXXXXX + // matches[2] = prefix (eg 1), matches[4] = drop (eg 613), matches[5] = rest of number (eg NXXXXX) + + $drop = $matches[4]; + $prefix = $matches[2]; + $static = $matches[5]; + } else { + if (!is_null($agi)) { + $agi->verbose('Could not understand pattern "'.$pattern.'" ('.$regex.')', 1); + } + return false; + } + + // convert asterisk pattern matching into perl regular expression + $regex = str_replace( + array( + "X", + "Z", + "N", + ".", + "*", + ), + array( + "[0-9]", + "[1-9]", + "[2-9]", + "[0-9#*]+", + "\*", + ), + // note, we're doing a subpattern match on the static portion so it can be extracted later + $drop.'('.$static.')'); + + if (preg_match('/^'.$regex.'$/', $number, $matches)) { + return $prefix.$matches[1]; + } + return false; +} + +/**********************************************************************************************************************/ + +$agi = new AGI(); + +$localprefix_file = get_var($agi, "ASTETCDIR")."/localprefixes.conf"; + +if (file_exists($localprefix_file)) { + parse_conf($localprefix_file, $conf, $section); + if (count($conf) == 0) { +// $agi->verbose("Could not parse ".$localprefix_file); + exit(1); + } +} else { + $agi->verbose("Could not open ".$localprefix_file); + exit(1); +} + +$r = $agi->get_variable("DIAL_NUMBER"); +if ($r["result"] == 0) { + $agi->verbose("DIAL_NUMBER not set -- nothing to do"); + exit(1); +} +$number = $r["data"]; + +$number = sanitizeNumber($number); + +$r = $agi->get_variable("DIAL_TRUNK"); +if ($r["result"] == 0) { + $agi->verbose("DIAL_TRUNK not set -- nothing to do"); + exit(1); +} +$trunk = $r["data"]; + + +if (isset($conf["trunk-$trunk"])) { + foreach ($conf["trunk-$trunk"] as $key=>$rule) { + // extract all ruleXX keys + //$agi->conlog("$key = $rule"); + if (preg_match("/^rule\d+$/",$key)) { + // $rule is a dial rule + + if (($newnum = fixNumber($rule, $number, $agi)) !== false) { + $agi->verbose('Dialpattern '.$rule.' matched. '.$number.' -> '.$newnum, 2); + $agi->set_variable("DIAL_NUMBER", $newnum); + + // reverted back form r2080 (r2081 on trac) so that any pattern match will end + // the loop. This needs to stay like this for backwards compatibility and often + // patterns are used to avoid matching substitution rules for exception cases. + exit(0); + } // else, it didn't match this rule + } // else, this isn't a rule + } +} // else, no config for this section + +// we just exit with no changes to the variable. +exit(0); + +?> Index: /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/agi-bin/dialparties.agi =================================================================== --- /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/agi-bin/dialparties.agi (revision 6596) +++ /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/agi-bin/dialparties.agi (revision 6596) @@ -0,0 +1,772 @@ +#!/usr/bin/php -q + +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// Amended by Coalescent Systems Inc. Sept, 2004 +// to include support for DND, Call Waiting, and CF to external trunk +// info@coalescentsystems.ca +// +// This script has been ported to PHP by +// Diego Iastrubni and the FreePBX community + +/* --------WARNING--------- + * + * This script is auto-copied from an included module and will get overwritten. + * If you modify it, you must change it to write only, in the agi-bin directory, + * to keep it from getting changed. + */ + +require_once "phpagi.php"; +require_once "phpagi-asmanager.php"; + +$ext = array(); // Hash that will contain our list of extensions to call +$ext_hunt = array(); // Hash that will contain our list of extensions to call used by huntgroup +$cidnum = ""; // Caller ID Number for this call +$cidname = ""; // Caller ID Name for this call +$timer = ""; // Call timer for Dial command +$dialopts = ""; // options for dialing +$rc = ""; // Catch return code +$priority = ""; // Next priority +$rgmethod = ""; // If Ring Group what ringing method was chosen +$screen = false; // initialize screen variable +$dsarray = array(); // This will hold all the dial strings, used to check for duplicate extensions + +$AGI = new AGI(); +debug("Starting New Dialparties.agi", 1); + +// Get required channels variables that used to come from amportal.conf +$ampmgruser = get_var( $AGI, "AMPMGRUSER" ); +$ampmgrpass = get_var( $AGI, "AMPMGRPASS" ); +$cwinusebusy = get_var( $AGI, "CWINUSEBUSY" ); +$ast_version = get_var( $AGI, "ASTVERSION" ); +$chan_dahdi = get_var( $AGI, "ASTCHANDAHDI" ); + +$cwignore = get_var( $AGI, "CWIGNORE" ); +$cwignore = strtoupper(trim($cwignore)); +$cfignore = get_var( $AGI, "CFIGNORE" ); +$cfignore = strtoupper(trim($cfignore)); + +$astman = new AGI_AsteriskManager( ); +if (!$astman->connect("127.0.0.1", $ampmgruser , $ampmgrpass)) { + exit (1); +} + +$priority = get_var( $AGI, "priority" ) + 1; + +// Caller ID info is stored in $request in AGI class, passed from Asterisk +$cidnum = $AGI->request['agi_callerid']; +$cidname = $AGI->request['agi_calleridname']; +debug("Caller ID name is '$cidname' number is '$cidnum'", 1); + +$queue_wait = get_var( $AGI, "QUEUEWAIT" ); +if ($queue_wait != '') { + $saved_cidname = get_var( $AGI, "SAVEDCIDNAME" ); + $elapsed = round((time() - $queue_wait)/60,0); + if ($saved_cidname == '') { + $AGI->set_variable('__SAVEDCIDNAME',$cidname); + $AGI->set_variable('CALLERID(name)',"M$elapsed:$cidname"); + } else { + $AGI->set_variable('CALLERID(name)',"M$elapsed:$saved_cidname"); + } +} + +// From this point forward, Set KEEPCID in the channel so subsequent calls, CF, etc. retain the incoming +// CID that get sent down channel local. +$AGI->set_variable('__KEEPCID','TRUE'); + +// Set to '' in case it was previously set +// +$AGI->set_variable('DIALSTATUS_CW',''); + +$timer = get_var( $AGI, "ARG1" ); +$dialopts = get_var( $AGI, "ARG2" ); +$rgmethod = get_var( $AGI, "RingGroupMethod" ); +$rgmethod = trim($rgmethod); + +$alertinfo = get_var( $AGI, "ALERT_INFO" ); +if ($alertinfo) { + debug("Setting Alert-Info: $alertinfo", 4); + $AGI->set_alertinfo($alertinfo); +} + +$sippheader = get_var( $AGI, "SIPADDHEADER" ); +if ($sippheader) { + $fields = explode(':',$sippheader,2); + debug("Setting sipheader ".$fields[0].": ".$fields[1], 4); + $AGI->exec_sipaddheader($fields[0], $fields[1]); +} + +$pr_dialstatus = get_var( $AGI, "PR_DIALSTATUS" ); + +$fmgrp = get_var( $AGI, "FMGRP" ); +$nodest = get_var( $AGI, "NODEST" ); +if (empty($nodest)) { + $nodest = ''; +} + +$ringgroup_index = get_var( $AGI, "RINGGROUP_INDEX" ); +$use_confirmation = get_var( $AGI, "USE_CONFIRMATION" ); +if (empty($use_confirmation)) { + $use_confirmation = "FALSE"; +} +debug("USE_CONFIRMATION: '$use_confirmation'", 5); +debug("RINGGROUP_INDEX: '$ringgroup_index'", 5); + +if (empty($timer)) { + $timer = 0; +} +if (empty($dialopts)) { + $dialopts = ""; +} +if (empty($rgmethod)) { + $rgmethod = "none"; +} + +debug("Methodology of ring is '$rgmethod'", 1); + +// reset the ringgroup method to its fundamental algorithm and pull out if +// master mode. + +$recall_mastermode=$rgmethod; + +switch ($rgmethod) { + case 'ringall-prim': + $rgmethod = "ringall"; + $mastermode = 1; + break; + case 'hunt-prim': + $rgmethod = "hunt"; + $mastermode = 1; + break; + case 'memoryhunt-prim': + $rgmethod = "memoryhunt"; + $mastermode = 1; + break; + case 'ringallv2-prim': + $rgmethod = "ringallv2"; + $mastermode = 1; + break; + default: + $mastermode = 0; + $pr_dialstatus = ""; // not relevant if not mastermode, clear it so dnd doesn't propagate, and other +} + +// call confirmation only works with ringall and ringall-prim. The javascripts in ringgroups +// and follow-me should enforce this. If that has been overridden then force ringall. +// Keep this code after the matermode check above, since they will at least get mastermode +// if they set a -prim mode in one of the others +// +if ( ($use_confirmation != "FALSE") && ($rgmethod != "ringall") && ($rgmethod != "ringallv2") && ($rgmethod != "hunt") ) { + debug("Unsupported RingMethod: '$rgmethod' resetting to ringall", 1); + $rgmethod = "ringall"; +} + +// Start with Arg Count set to 3 as two args are used +$arg_cnt = 3; +while($arg = get_var($AGI,"ARG". $arg_cnt)) { + // not sure why, dialparties will get stuck in a loop if noresponse + if ($arg == '-') { + debug("get_variable got a \"noresponse\"! Exiting",3); + exit($arg_cnt); + } + $extarray = split( '-', $arg ); + foreach ( $extarray as $k ) { + $ext[] = $k; + debug("Added extension $k to extension map", 3); + } + $arg_cnt++; +} + +// FollowMe Preparation for Pre-Ring: +// +// If the primary extension is in the ringgroup list, then it should be rung +// during both the pre-ring time and the list time, so it's real prering time +// is the entire time. If it is not in the list, then it should only ring +// for the pre-ring time. This section determines the times and then adds it +// to the list if not already there, so that the dialstring is computed +// appropriately. This section also makes sure that the primary extension +// is at the top of the list. +// +// Notes before I forget. The primary may have been in the list and screwed +// above. So ... do I need to move this up, probably. +// +if ($rgmethod == "ringallv2" && $fmgrp != "") { + + $fmgrp_primaryremoved = 0; + + $fmgrp_prering = $AGI->database_get('AMPUSER', $fmgrp."/followme/prering"); + $fmgrp_prering = $fmgrp_prering['data'] > 1 ? $fmgrp_prering['data'] : 2; + + $fmgrp_grptime = $AGI->database_get('AMPUSER', $fmgrp."/followme/grptime"); + $fmgrp_grptime = $fmgrp_grptime['data']; + debug("got fmgrp_prering: $fmgrp_prering, fmgrp_grptime: $fmgrp_grptime",4); + + $fmgrp_totalprering = $fmgrp_grptime + $fmgrp_prering; + debug("fmgrp_totalprering: $fmgrp_totalprering",4); + + if (in_array($fmgrp, $ext)) { + debug("found extension in pre-ring and array",4); + $fmgrp_realprering = $fmgrp_totalprering; + if ($ext[0] != $fmgrp) { + $tmpx=array_flip($ext); + unset($ext[$tmpx[$fmgrp]]); + array_unshift($ext,$fmgrp); + } + } else { + debug("extension not in group list, ringging only during prering time",4); + $fmgrp_realprering = $fmgrp_prering; + array_unshift($ext,$fmgrp); + } + debug("ringallv2 ring times: REALPRERING: $fmgrp_realprering, PRERING: $fmgrp_prering",4); +} +$already_screened = get_var( $AGI, "SCREEN" ); // If this is the second pass through dialparties.agi, we don't want to double-screen the caller +$from_outside = get_var( $AGI, "FROM_DID" ); +$astvarlibdir = get_var( $AGI, "ASTVARLIBDIR" ); +// If this isn't a ring group, check to see if the user has call screening on +// Only screen calls if the primary extension is called, or it's follow-me is called, not ring groups +if(($from_outside != '') && isset($ext[0]) && ($rgmethod == "none" || $fmgrp == $ext[0]) && !$already_screened) { + $screen_call = $AGI->database_get('AMPUSER', $ext[0]."/screen"); + $screen_call = $screen_call['data']; + if (strlen($screen_call)) { + if($screen_call == 'nomemory' && $cidnum != '') { // This can't go in the dialplan because macro-dial can get called multiple times + // Do a security check, we only numeric callerid numbers, otherwise code could be incjected in a cidnum field + // that could result in an arbitrary command being executed in this remove operation. + if (ctype_digit($cidnum)) { + exec("rm -f $astvarlibdir/sounds/priv-callerintros/$cidnum.*"); + } + } + $screen = true; + $AGI->set_variable('__SCREEN',$screen_call); + $AGI->set_variable('__SCREEN_EXTEN',$ext[0]); + debug("Extension $k has call screening on", 4); + } else { + debug("Extension $k has call screening off", 4); + } +} +// IF THE FIRST EXTENSION IS CALL FORWARD ENABLED (put in logic) then we don't do master mode +// which means we reset the flag here after detecting that and just say we are not in master +// mode and all is well. That means the loop below needs to be modified to detect the first +// extension and do this if the case. + +// Check for call forwarding first +// If call forward is enabled, we use chan_local +// Hacky. We should be using an associative array, shouldn't we? +$count = 0; +foreach ($ext as $k) { + $cf = $AGI->database_get('CF',$k); + $cf = $cf['data']; + // + // TODO: If ringgoup tells us to ignore CF then don't do this + // + if (strlen($cf)) { + // append a hash sign so we can send out on chan_local below unless cfingore is set + if ($cfignore) { + unset($ext[$count]); + debug("Extension $k has call forward set to $cf", 1); + $AGI->set_variable('DIALSTATUS','NOANSWER'); + } else { + $ext[$count] = $cf.'#'; + debug("Extension $k has call forward set to $cf", 1); + } + + // if this is the primary extension and CF enabled, then cancel mastermode + // whether it is or not, no need to check. + // + if ($count == 0) { + $mastermode = 0; + $pr_dialstatus = ""; // not relevant if not mastermode, clear it so dnd doesn't propagate, and other + debug("Primary ext is CF so disabling mastermode if it was set", 4); + } + } + else { + debug("Extension $k cf is disabled", 3); + } + $count++; +} + +// IF DND AND we process it as a DND (no CF enabled) then we need to some how flag that ALL THE REST +// should now be ignored and not processed if in master mode (and this primary). Do this by setting some +// sort of flag that says master mode DND so skip everything else below (set them all to ""). +// + +// Hacky. +$count = 0; +$dndprimary = 0; +// Now check for DND +foreach ( $ext as $k ) { + if ( (substr($k,-1)!='#') ) { + // no point in doing if cf is enabled + $dnd = $AGI->database_get('DND',$k); + $dnd = $dnd['data']; + if (strlen($dnd) || $pr_dialstatus == "BUSY") { + debug("Extension $k has do not disturb enabled, or followme pre-ring returned busy", 2); + unset($ext[$count]); + $AGI->set_variable('DIALSTATUS','BUSY'); + // if this is primary set dndprimary and figure out if needed below + // + if ($count == 0 && $mastermode) { + $dndprimary = 1; + debug("Primary extension is DND, so if mastermode, all should be dnd", 4); + } + if ($count == 0) { + $fmgrp_primaryremoved = 1; + } + } + else { + debug("Extension $k do not disturb is disabled", 3); + } + } + $count++; +} + +// Main calling loop +// +$skipremaining = 0; // used to allow primary to ring but skip the rest +$ds = ''; +foreach ( $ext as $k ) { + // mastermode description: + // + // if mastermode is set then the first extension will be examined and mastermode will be reset so that the others + // are left alone. If the remaining extensions are not to be tried, skpremaining will be set to 1 which will + // result in dndprimary being set to 1 thus diabling the remaining list. + // + // if cf unconditional was already detected on the primary, then mastermode will have been reset at this point + // since that will negate the mastermode concpet. + // + // if dnd was set on the primary then dndprimary will already be set resulting in a completly blanked out list + // since dnd on the primary means don't bother me on any. + + // Don't bother checking these if we will be blanking the extnum anyhow + if ($skipremaining == 1) { + $dndprimary = 1; + } + if ($dndprimary == 0) { + // TODO what are these comments...? do we need to remove them...? + $extnum = $k; + + // CWIGNORE is sent down the channel when all extensions should be treated as if they do not have + // call waiting enabled. This is used primarily by Queue type setups (sometimes Ring Groups) when + // you want to assure that calls go on to the next agent if the current one is on the phone instead + // of ringing their line constantly. + // + if ($cwignore) { + $exthascw = 0; + } else { + $exthascw = $AGI->database_get('CW', $extnum);// ? 1 : 0; + $exthascw = $exthascw['data']? 1:0; + } + + $extcfb = $AGI->database_get('CFB', $extnum);//? 1 : 0; + $extcfb = $extcfb['data']; + $exthascfb = (strlen($extcfb) > 0) ? 1 : 0; + $extcfu = $AGI->database_get('CFU', $extnum);// ? 1 : 0; + $extcfu = $extcfu['data']; + $exthascfu = (strlen($extcfu) > 0) ? 1 : 0; + + // Dump details in level 4 + debug("extnum $extnum has: cw: $exthascw; hascfb: $exthascfb [$extcfb] hascfu: $exthascfu [$extcfu]",4); + + // check if mastermode and then reset here. If mastermode, this will be the first extension so + // the state is checked and a decision is made as to what to do. We have gotten all the cf variables + // above. If CF unconditional was set, we never get here because we alread reset mastermode. If DND + // were set then we never get here becasue didprimary was set + if ($mastermode == 1) { + $mastermode = 0; + $extstate = is_ext_avail($extnum); + debug("Extension $extnum has ExtensionState: $extstate",1); + + if ( ($exthascw == 1) && ($extstate == 1) ) { + // process this one extension but the remaining should be skipped since there is cw and + // the extension is occupied. This will try this extension but not the others. + $skipremaining = 1; + debug("In mastermode with cw enabled so $extnum will be tried and others skipped",4); + } elseif ( ($exthascw == 0) && ($extstate == 1) ) { + // no cw, ext is busy. So if cfb is set, it will forward there and if not, it will be + // ignored as normal behavior. In either case, we skip the remaining numbers. + $skipremaining = 1; + debug("In mastermode with cw disabled so $extnum will be processed in case cfb set",4); + } + // All other cases should act like normal. Unavailable, not busy, ringing, etc. + // should not be effected + } + } // end if ($dndprimary == 0) + // $dndprimary == 1 so clear the extension + else { + // clear the current extension if dndprimary has been set. This will only be the case if in mastermode so no need to check + // that. Use this to skip remaining extensions also if just ringing the primary. + $extnum = ''; + } + + // if CF is not in use and $dndprimary is not set otherwise $extnum has been cleared and nothing to do + // + if ( (substr($k,-1)!='#') && $dndprimary == 0) { + // CW is not in use or CFB is in use on this extension, then we need to check! + if ( ($exthascw == 0) || ($exthascfb == 1) || ($exthascfu == 1) ) { + // get ExtensionState: 0-idle; 1-busy; 4-unavail; 8-ringing <--- these are unconfirmed + $extstate = is_ext_avail($extnum); + debug("Extension $extnum has ExtensionState: $extstate",1); + + // Ext has CFU and is Unavailable + if ( ($exthascfu == 1) && ($extstate == 4) ) { + // If part of a ring group, then just do what CF does, otherwise needs to + // drop back to dialplan with NOANSWER + + // + // If cfignore is set, then we don't honor any CF settings + // + if ($rgmethod != '' && $rgmethod != 'none' && !$cfignore) { + debug("Extension $extnum has call forward on no answer set and is unavailable and is part of a Ring Group forwarding to '$extcfu'",1); + $extnum = $extcfu . '#'; # same method as the normal cf, i.e. send to Local + } else { + debug("Extension $extnum has call forward on no answer set and is unavailable",1); + $extnum = ''; + $AGI->set_variable('DIALSTATUS','NOANSWER'); + } + } elseif ( ($exthascw == 0) || ($exthascfb == 1) ) { + debug("Checking CW and CFB status for extension $extnum",3); + // extension in use + if ($extstate > 0 && $extstate != 4) { + debug("Extension $extnum is not available to be called", 1); + // extension in use + + // don't honor any CF settings when $cfignore is set + // + if ($exthascfb == 1 && !$cfignore) { + debug("Extension $extnum has call forward on busy set to $extcfb",1); + $extnum = $extcfb . '#'; # same method as the normal cf, i.e. send to Local + // CW not in use + } elseif ($exthascw == 0) { + debug("Extension $extnum has call waiting disabled",1); + $extnum = ''; + $AGI->set_variable('DIALSTATUS','BUSY'); + } else { + debug("Extension $extnum has call waiting enabled",1); + } + } + // -1 means couldn't read status usually due to missing HINT + } elseif ($extstate < 0) { + debug("ExtensionState for $extnum could not be read...assuming ok",3); + } else { + debug("Extension $extnum is available",1); + } + } elseif ($rgmethod == "none" && $exthascw == 1 && $cwinusebusy) { + $extstate = is_ext_avail($extnum); + if ($extstate == 1) { + $AGI->set_variable('DIALSTATUS_CW','BUSY'); + debug("Extension $extnum has call waiting enabled with state: $extstate",1); + } + // get ExtensionState: 0-idle; 1-busy; 4-unavail; 8-ringing <--- these are unconfirmed + } elseif ( ($exthascw == 1) && ($rgmethod == 'firstnotonphone') ) { + $extstate = is_ext_avail($extnum); + debug("Extension $extnum has ExtensionState: $extstate",1); + // CW in use - but blocked for hunt + if ($extstate == 1) { + debug("Extension $extnum has call waiting enabled but blocked for hunt",1); + $extnum = ''; + $AGI->set_variable('DIALSTATUS','BUSY'); + } + } + } + + if ($extnum != '') { + // Still got an extension to be called? + // check if we already have a dial string for this extension + // if so, ignore it as it's pointless ringing it twice ! + $realext = str_replace("#", "", $extnum); + if ( isset($dsarray[$realext]) ) { + debug("Extension '$realext' already in the dialstring, ignoring duplicate",1); + } else { + $dsarray[$realext] = 1; // could be dial string i suppose but currently only using for duplicate check + $extds = get_dial_string( $AGI, $extnum, $use_confirmation, $ringgroup_index); + if (strlen($extds)) { + $ds .= $extds . '&'; + } + // Update Caller ID for calltrace application + if ((substr($k,-1)!='#') && (($rgmethod != "hunt") && ($rgmethod != "memoryhunt") && ($rgmethod != "firstavailable") && ($rgmethod != "firstnotonphone")) ) { + if ( isset($cidnum) && is_numeric($cidnum) ) { + $rc = $AGI->database_put('CALLTRACE', $k, $cidnum); + if ($rc['result'] == 1) { + debug("dbset CALLTRACE/$k to $cidnum", 3); + } else { + debug("Failed to DbSet CALLTRACE/$k to $cidnum ({$rc['result']})", 1); + } + } else { + // We don't care about retval, this key may not exist + $AGI->database_del('CALLTRACE', $k); + debug("DbDel CALLTRACE/$k - Caller ID is not defined", 3); + } + } else { + $ext_hunt[$k]=$extds; // Need to have the extension HASH set with technology for hunt group ring + } + } + } +} // end foreach ( $ext as $k ) + +$dial_filtered = implode('-',array_keys($dsarray)); +$AGI->set_variable('FILTERED_DIAL',$dial_filtered); +debug("Filtered ARG3: $dial_filtered", 3); + +$dshunt = ''; +$loops = 0; +$myhuntmember = ""; + +/** Here we setup the Channel Variables that are used to do the dialing, in all cases you will have: + * ${HuntMembers} set to the number of phones to ring + * ${HuntMemberN} set to the dial pattern that should be dialed. (N is 0, 1, 2 etc.) + */ +if (($rgmethod == "hunt") || ($rgmethod == "memoryhunt") || ($rgmethod == "firstavailable") || ($rgmethod == "firstnotonphone")) { + if ($cidnum) { + $AGI->set_variable('CALLTRACE_HUNT',$cidnum); + } + foreach ($extarray as $k ) { + // we loop through the original array to get the extensions in order of importance + if ($ext_hunt[$k]) { + //If the original array is included in the extension hash then set variables + $myhuntmember="HuntMember"."$loops"; + if (($rgmethod == "hunt") || ($rgmethod == "firstavailable") || ($rgmethod == "firstnotonphone")) { + $AGI->set_variable($myhuntmember,$ext_hunt[$k]); + } elseif ($rgmethod == "memoryhunt") { + if ($loops==0) { + $dshunt =$ext_hunt[$k]; + } else { + $dshunt .='&'.$ext_hunt[$k]; + } + $AGI->set_variable($myhuntmember,$dshunt); + } + $loops += 1; + } + } +} + +$ds = chop($ds," &"); + +if ($nodest != '' && $use_confirmation == 'FALSE') { + if (strpos($dialopts,"M(auto-blkvm)") > 0 || strpos($dialopts,"M(auto-blkvm)") === 0 || + strpos($dialopts,"M(auto-confirm") > 0 || strpos($dialopts,"M(auto-confirm") === 0 || + strpos($dialopts,"M(confirm") > 0 || strpos($dialopts,"M(confirm") === 0) { + debug("NODEST: $nodest blkvm enabled macro already in dialopts: $dialopts",4); + } else { + $dialopts .= "M(auto-blkvm)"; + debug("NODEST: $nodest adding M(auto-blkvm) to dialopts: $dialopts",4); + } +} + +// FollowMe Changes: +// +// We need to determine if the generated dialstring can be dialed as is. This will be the case if there are no +// or is only a single extension to dial. +// +// First, unset any blank fields so we know how many extensions there are to call. +// +// If mastermode (skipremaining == 1) was triggered then we just set the ringtime to what the primary extension +// should ring for and let this dialstring go. +// +// If there is only one extension in the list, then we need to determine how long to ring it (depending on if it +// was the primary or another extension, then let the generated dialstring ring it. +// +// Otherwise, we need to re-create the dialstring to be processed by our special dialplan that will ring the +// primary extension and hold the group list for the required delay. Also - if we are in a call confirmation mode +// then we need to reset the call confirm variables with one level of inheritance so that they remain in the new +// channels but don't get further propogated after that. We also clear it for the remainder of this instance since +// we are not yet triggering further actions until the next call. +// +// Notes: $fmgrp_primaryremoved is set to 1 if the primary has been removed from the list so we know that it was dnd-ed. +// this only matters in non-prim mode, where we need to know if the remaining list contains the primary extension +// or not. +// +if ($rgmethod == 'ringallv2') { + $count = 0; + foreach ($ext as $x) { + if ($x == '') { + unset($ext[$count]); + } + $count++; + } + + if (($skipremaining == 1) || (count($ext) == 1 && $fmgrp_primaryremoved == 0)) { + $timer = $fmgrp_realprering; + } elseif (count($ext) == 1 && $fmgrp_primaryremoved == 1) { + $timer = $fmgrp_grptime; + } elseif (count($ext) == 1) { + $timer = $fmgrp_totalprering; // not sure what would trigger this ? + } else { + $timer = $fmgrp_totalprering; + $ds = "Local/FMPR-".array_shift($ext)."@from-internal&Local/FMGL-".implode('-',$ext)."@from-internal"; + + $fmgrp_fmunique = $AGI->request['agi_channel']; + $AGI->set_variable('_FMUNIQUE',$fmgrp_fmunique); + $AGI->set_variable('_RingGroupMethod',"ringall"); + $fmgrp_prering -= 2; + $AGI->set_variable('_FMPRERING',$fmgrp_prering); + $AGI->set_variable('_FMREALPRERING',$fmgrp_realprering); + $AGI->set_variable('_FMGRPTIME',$fmgrp_grptime); + $AGI->set_variable('_FMPRIME',($recall_mastermode == "ringallv2")?"FALSE":"TRUE"); + + debug("FMUNIQUE: $fmgrp_fmunique, FMRERING: $fmgrp_prering, FMREALPRERING: $fmgrp_realprering, FMGRPTIME: $fmgrp_grptime",6); + + if ($use_confirmation != 'FALSE') { + $AGI->set_variable('_USE_CONFIRMATION',$use_confirmation); + $AGI->set_variable('_RINGGROUP_INDEX',$ringgroup_index); + $use_confirmation = 'FALSE'; + } + } +} + +if ($nodest != '' && $use_confirmation == 'FALSE') { + if (strpos($dialopts,"M(auto-blkvm)") > 0 || strpos($dialopts,"M(auto-blkvm)") === 0 || + strpos($dialopts,"M(auto-confirm") > 0 || strpos($dialopts,"M(auto-confirm") === 0 || + strpos($dialopts,"M(confirm") > 0 || strpos($dialopts,"M(confirm") === 0) { + debug("NODEST: $nodest blkvm enabled macro already in dialopts: $dialopts",4); + } else { + $dialopts .= "M(auto-blkvm)"; + debug("NODEST: $nodest adding M(auto-blkvm) to dialopts: $dialopts",4); + } +} + +if (!strlen($ds)) { + $AGI->noop(''); +} else { + // Asterisk 1.6 uses , instead of | and 1.4 can't recieve a , in the ds. + // + if (version_compare($ast_version, "1.6", "ge")) { + $ds_seperator = ','; + } else { + $ds_seperator = '|'; + } + + if (($rgmethod == "hunt") || ($rgmethod == "memoryhunt") || ($rgmethod == "firstavailable") || ($rgmethod == "firstnotonphone")) { + $ds = $ds_seperator; + if ($timer) { + $ds .= $timer; + } + $ds .= $ds_seperator . $dialopts; // pound to transfer, provide ringing + $AGI->set_variable('ds',$ds); + $AGI->set_variable("HuntMembers",$loops); + $AGI->set_priority("huntdial"); // dial command was at priority 20 where dialplan handles calling a ringgroup with strategy of "hunt" or "MemoryHunt" + } else { + $ds .= $ds_seperator; + if ($timer) { + $ds .= $timer; + if (trim($use_confirmation) != "FALSE") { + $AGI->set_variable('__RT',$timer); + } + } + $ds .= $ds_seperator . $dialopts; // pound to transfer, provide ringing + if ($screen) { + $ds .= "p"; + } + if (trim($use_confirmation) != "FALSE") { + $AGI->set_variable('__RG_IDX',$ringgroup_index); + if ( isset($cidnum) && is_numeric($cidnum) ) { + $AGI->set_variable('__CALLCONFIRMCID',$cidnum); + } else { + $AGI->set_variable('__CALLCONFIRMCID',"999"); + } + } + $AGI->set_variable('ds',$ds); + $AGI->set_priority("normdial"); // dial command was at priority 10 + } +} + +// sanity check make sure dialstatus is set to something +// +if (! $ds) { + $dialstatus = get_var( $AGI, "DIALSTATUS" ); + if (! $dialstatus) { + debug("Setting default NOANSWER DIALSTATUS since no extensions available",1); + $AGI->set_variable('DIALSTATUS','NOANSWER'); + } +} + +$astman->disconnect(); + +// EOF dialparties.agi +exit( 0 ); + +// helper functions +function get_var( $agi, $value) { + $r = $agi->get_variable( $value ); + + if ($r['result'] == 1) { + $result = $r['data']; + return $result; + } + return ''; +} + +function get_dial_string( $agi, $extnum, $use_confirmation, $ringgroup_index ) { + global $chan_dahdi; + $dialstring = ''; + + if (strpos($extnum,'#') != 0) { + // "#" used to identify external numbers in forwards and callgourps + // If using call confirmation, need to put the # back into the new dialstring + // we then place all external calls (denoted with a # at the end) through + // the [grps] extension for the RINGGROUP_INDEX that was called. This + // triggers the call confirmation macro along with the required messages + // that were set. + // + $extnum = str_replace("#", "", $extnum); + if (trim($use_confirmation) == "FALSE") { + $dialstring = 'Local/'.$extnum.'@from-internal/n'; + } else { + $dialstring = 'Local/RG-'.$ringgroup_index.'-'.$extnum.'#@from-internal'; + } + debug("Built External dialstring component for $extnum: $dialstring", 4); + } else { + $device_str = sprintf("%s/device", $extnum); + $device = $agi->database_get('AMPUSER',$device_str); + $device = $device['data']; + + // a user can be logged into multipe devices, append the dial string for each + $device_array = split( '&', $device ); + foreach ($device_array as $adevice) { + if (trim($use_confirmation) == "FALSE") { + $dds = $agi->database_get('DEVICE',$adevice.'/dial'); + if ($chan_dahdi) { + $dialstring .= str_replace('ZAP/', 'DAHDI/', $dds['data']); + } else { + $dialstring .= $dds['data']; + } + $dialstring .= '&'; + } else { + $dialstring .= 'Local/LC-'.$adevice.'@from-internal&'; + } + } + $dialstring = trim($dialstring," &"); + } + return $dialstring; +} + +function debug($string, $level=3) { + global $AGI; + $AGI->verbose($string, $level); +} + +function mycallback( $rc ) { + debug("User hung up. (rc=" . $rc . ")", 1); + exit ($rc); +} + +function is_ext_avail( $extnum ) { + global $astman; + + $status = $astman->ExtensionState( $extnum, 'from-internal' ); + + $status = $status['Status']; + debug("ExtensionState: $status", 4); + return $status; +} + +?> Index: /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/agi-bin/directory =================================================================== --- /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/agi-bin/directory (revision 4457) +++ /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/agi-bin/directory (revision 4457) @@ -0,0 +1,520 @@ +#!/usr/bin/php -q +get_variable( $value ); + + if ($r['result'] == 1) + { + $result = $r['data']; + return $result; + } + else + return ''; +} + +function output(&$var) { + if (DEBUG) { + global $logfile; + + if (!isset($logfile)) return false; + + ob_start(); + var_dump($var); + $output = ob_get_contents(); + ob_end_clean(); + fwrite($logfile, $output); + } +} + + +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], + ); + + // parse options + foreach (explode("|",$matches[6]) as $opt) { + $temp = explode("=",$opt); + 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]; + } + } + } +} + + +/** Play a bunch of files, optionally accepting input and looping + * @param $files The file/files to play (for multiple files, pass array) + * @param $escape_digits The DTMF tones that can be pressed & returned, ie, "123*" + * @param $timeout The timeout waiting for a digit, or 0 to not wait at all. + * @param $max_digits Maximum number of digits to get. **NOT IMPLEMENTED YET.. 1 only** + * @param $loop False for no loop, or an integer >0 being the number of times to loop. + * @param $loopreturn Value to return when the loop expires +*/ +function stream_multiple($files, $escape_digits = "", $timeout = 0, $max_digits = 1, $loop = false, $loopreturn = 0) { + global $agi; + + if (!is_array($files)) { + $files = array($files); + } + + $i = 0; + do { + foreach ($files as $file) { + $agi->verbose("-- Playing '".$file."' (language 'en')"); + $r = $agi->stream_file($file, $escape_digits); + $agi->conlog("stream_multiple: $file returned ".$r["result"]); + switch ($r["result"]) { + case 0: // they did nothing + break; + case -1: // they hungup + $agi->verbose("remote user hungup"); + return -1; + break; + default: // they pressed a key + return $r["result"]; + break; + } + } + + if ($timeout > 0) { + $r = $agi->wait_for_digit($timeout); + if (($r["result"] != 0) || (!$loop)) { + // return only if the reult is not 0 (timeout) + // or we're not doing a loop + return $r["result"]; + } + } + + if ($loop && (++$i > $loop)) { + return $loopreturn; + } + } while ($loop); + + return 0; +} + +/** If $file.(wav|WAV|gsm|GSM) exists + */ +function sound_file_exists($file) { + global $agi; + + foreach (array("gsm","GSM","wav","WAV") as $ext) { + if (file_exists($file.".".$ext)) { + $agi->verbose("Found ".$file.".".$ext, 2); + return true; + } + } + return false; +} + +function string_to_digits($string) { + $out = ""; + + for($i=0; $iget_data($intro, 4000, NUM_DIGITS); + + if (($r["result"] == "0") && $operator) { + // operator + $agi->verbose("Dropping to operator"); + // switch to o,1 in the current context + $agi->set_extension("o"); + $agi->set_priority("1"); + // exiting application immediately! + exit(0); + } + $digits = $r["result"]; + + usleep(500); // pause a bit, so digit presses get cleared + + if ($digits !== "") { + // they entered SOMETHING, reset our loop + $loop = 0; + } + + $i = 0; + $digit = false; + if (isset($directory[$digits]) && isset($directory[$digits][$i])) { + $loop = 0; // reset loop counter + do { + $match = & $directory[$digits][$i]; + + $maindirname = sprintf($voicemail_dir, $match["context"], $match["ext"]); + + if (sound_file_exists($maindirname."/greet")) { + $r = $agi->stream_file($maindirname."/greet",$escape_digits); + if ($r["result"] > 0) $digit = $r["result"]; + } else { + $digit = say_alpha($match["name"],$escape_digits); + } + + if (!$digit) { + $digit = stream_multiple("dir-instr", $escape_digits, 3000); + } + + switch ($digit) { + case D_1: // dial this + if ($say_exten) { + $agi->stream_file("pls-hold-while-try"); + $agi->stream_file("to-extension"); + + $agi->say_digits($match["ext"]); + } + $agi->conlog("Dial ".$match["ext"]); + + $agi->set_context($dial_context); + $agi->set_extension($match["ext"]); + $agi->set_priority("1"); + exit(0); + break; + case D_STAR: // not correct + $i += 1; + break; + case D_0: // operator + if ($operator) { + $agi->verbose("Dropping to operator"); + // switch to o,1 in the current context + $agi->set_extension("o"); + $agi->set_priority("1"); + // exiting application immediately! + exit(0); + } + break; + case -1: // hungup + $agi->conlog("User hungup"); + exit(1); + break; + case 0: // no response + $loop++; + break; + case -2: // loop timed out + $agi->stream_file("goodbye"); + $agi->hangup(); + exit(0); + break; + } + + if ($digit !== 0) { + $loop = 0; + } + $digit = false; + } while (isset($directory[$digits][$i]) && ($loop < MAX_REPEAT)); + + if (!isset($directory[$digits][$i])) { + $agi->stream_file("dir-nomore"); + $loop = 0; // reset our loop counter so it doesn't hangup + } + } else if (!empty($digits) || ($digits === "0")) { + // strict type checking as they may have entered "0" (string) which is empty() + $agi->stream_file("dir-nomatch"); + } // else, we timed out + + $loop++; + } +} + +/******************************************************************************/ + +$agi = new AGI; + +$directory_file = get_var($agi, "ASTETCDIR")."/voicemail.conf"; + +// where is voicemail stored? +$voicemail_dir = get_var($agi, "ASTSPOOLDIR")."/voicemail/%s/%d"; + +// where should we store logs? (fixes #1912) +$log_dir = get_var($agi, "ASTLOGDIR"); + +if (DEBUG) $logfile = fopen( $log_dir . "/directory.log","w"); + +$vmconf = array(); +$null = null; +parse_voicemailconf($directory_file, $vmconf, $null); + + +if (!$argv[1]) { + $agi->verbose("Notice: vm-context not specified. Using 'default'"); + $vm_context = "default"; +} else { + $vm_context = trim($argv[1]); +} + +if (!isset($vmconf[$vm_context]) && ($vm_context != "general")) { + // we make an exception for "general" context,as it just includes other contexts + $agi->verbose("Cannot find context ".$vm_context." in ".$directory_file); + exit(1); +} + +if (isset($argv[2])) { + $dial_context = trim($argv[2]); +} else { + $dial_context = $vm_context; +} + +$operator = false; +$dir_type = DIR_FIRST; +$say_exten = false; + +if (isset($argv[3])) { + for($i=0; $i$arr) { + // skip if it's general context -- this doesn't contain mailboxes + if ($context == "general") continue; + + foreach ($arr as $key=>$box) { + // we could do if !isset($boxes[$key]) to NOT override mailboxes + + // add in the context, otherwise we don't know what it is + $box["context"] = $context; + + $boxes[$key] = $box; + } + } +} else { + $boxes = &$vmconf[$vm_context]; +} + +$directory = array(); +foreach ($boxes as $box) { + if (!empty($box["name"])) { + $name = explode(" ",$box["name"]); + + if (isset($box["context"])) { + $context = $box["context"]; + } else { + $context = $vm_context; + } + + switch ($dir_type) { + case DIR_FIRST: // first name only + $digits = string_to_digits($name[0]); + $directory[$digits][] = array("ext"=>$box["mailbox"], "name"=>$box["name"], "context"=>$context); + break; + case DIR_BOTH: // all names + foreach ($name as $temp) { + $digits = string_to_digits($temp); + $directory[$digits][] = array("ext"=>$box["mailbox"], "name"=>$box["name"], "context"=>$context); + } + break; + case DIR_LAST: default: // last name only + $digits = string_to_digits(end($name)); + $directory[$digits][] = array("ext"=>$box["mailbox"], "name"=>$box["name"], "context"=>$context); + break; + } + } +} + +if (DEBUG) { + output($argv); + output($dir_type); + output($directory); +} + +do_directory($dir_type, $directory, $dial_context, $say_exten, $operator); + +?> Index: /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/agi-bin/checksound.agi =================================================================== --- /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/agi-bin/checksound.agi (revision 4521) +++ /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/agi-bin/checksound.agi (revision 4521) @@ -0,0 +1,67 @@ +#!/usr/bin/php -q + vmx,n,TrySystem(/bin/ls ${ASTSPOOLDIR}/voicemail/${VMCONTEXT}/${ARG1}/${MODE}.[wW][aA][vV]) + */ + +require_once "phpagi.php"; + +$AGI = new AGI(); +$file = $argv[1]; + +if (file_exists($file.".wav") || file_exists($file.".WAV")) { + $AGI->set_variable('SYSTEMSTATUS','SUCCESS'); +} else { + $AGI->set_variable('SYSTEMSTATUS','APPERROR'); + debug("VmX requires: $file.wav or .WAV exist in order to function",1); +} + +// EOF dialparties.agi +exit( 0 ); + +// helper functions +function get_var( $agi, $value) +{ + $r = $agi->get_variable( $value ); + + if ($r['result'] == 1) + { + $result = $r['data']; + return $result; + } + else + return ''; +} + +function debug($string, $level=3) +{ + global $AGI; + $AGI->verbose($string, $level); +} + +?> Index: /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/agi-bin/list-item-remove.php =================================================================== --- /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/agi-bin/list-item-remove.php (revision 4291) +++ /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/agi-bin/list-item-remove.php (revision 4291) @@ -0,0 +1,58 @@ +#!/usr/bin/php -q +verbose("Missing list"); + exit(1); +} + +if (!isset($argv[2])) { + $agi->verbose("Missing item"); + exit(1); +} + +if (!isset($argv[3])) { + $agi->verbose("Missing return var name"); + exit(1); +} + +$arglist = $argv[1]; +$argitem = $argv[2]; +$argvarname = $argv[3]; + +if (isset($argv[4])) { + $argsep = "&"; +} else { + $argsep = $argv[4]; +} + +$newlist = str_replace($argitem.$argsep, "", $arglist.$argsep); + +if (substr($newlist, -1, 1) == $argsep) { + $newlist = substr($newlist, 0, -1); +} + +$agi->set_variable($argvarname, $newlist); +?> Index: /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/agi-bin/recordingcheck =================================================================== --- /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/agi-bin/recordingcheck (revision 6521) +++ /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/agi-bin/recordingcheck (revision 6521) @@ -0,0 +1,132 @@ +#!/usr/bin/php -q +get_variable("ARG2"); + +switch($type['data']) { + case "Group": + + $r = $agi->get_variable("ARG1"); + if ($r["result"] == 0) { + $agi->verbose("Extension List not set -- nothing to do"); + exit(1); + } + $extenlist = $r["data"]; + + $agi->set_variable("RecEnable", "DISABLED"); //disable by default + + $list = explode("-",$extenlist); + if(!empty($list)) { + foreach($list as $exten) { + $setting = $agi->database_get("AMPUSER",$exten."/recording"); + if ($setting["result"] == 0) { + $agi->verbose("No DB Entry AMPUSER/$exten/recording - Not Recording for $exten, checking for others"); + continue; + } + //explode recording vars + $recording = explode("|",$setting["data"]); + $recout = substr($recording[0],4); + $recin = substr($recording[1],3); + if ($recin == "Always") { + $agi->verbose("Recording enable for ".$exten); + $agi->verbose("CALLFILENAME=g{$exten}-{$timestamp}-{$uniqueid}"); + $agi->set_variable("CALLFILENAME","g{$exten}-{$timestamp}-{$uniqueid}"); + $agi->set_priority('record'); + exit(0); + } + } + } else { + $agi->verbose("Extension List is empty -- nothing to do"); + exit(1); + } + + break; + case "OUT": + $exten = $agi->get_variable("ARG1"); + + $options = $agi->database_get("AMPUSER","{$exten['data']}/recording"); + + if ($options["result"] == "0") { + $agi->verbose("No AMPUSER db entry for ".$exten["data"].". Not recording"); + exit(1); + } + + //explode recording vars + $recording = explode("|",$options["data"]); + $recout = substr($recording[0],4); + $recin = substr($recording[1],3); + + if($recout == "Always") { + $agi->verbose("Outbound recording enabled."); + $agi->verbose("CALLFILENAME=OUT{$exten['data']}-{$timestamp}-{$uniqueid}"); + $agi->set_variable("CALLFILENAME","OUT{$exten['data']}-{$timestamp}-{$uniqueid}"); + $agi->set_priority('record'); + exit(0); + } else { + $agi->verbose("Outbound recording not enabled"); + exit(1); + } + break; + case "IN": + $exten = $agi->get_variable("ARG1"); + $options = $agi->database_get("AMPUSER","{$exten['data']}/recording"); + + if ($options["result"] == "0") { + $agi->verbose("No AMPUSER db entry for ".$exten["data"].". Not recording"); + exit(1); + } + //explode recording vars + $recording = explode("|",$options["data"]); + $recout = substr($recording[0],4); + $recin = substr($recording[1],3); + + if($recin == "Always") { + $agi->verbose("Inbound recording enabled."); + $agi->verbose("CALLFILENAME={$timestamp}-{$uniqueid}"); + $agi->set_variable("CALLFILENAME","{$timestamp}-{$uniqueid}"); + $agi->set_priority('record'); + exit(0); + } else { + $agi->verbose("Inbound recording not enabled"); + exit(1); + } + break; +} + +// we just exit with no changes to the variable. +exit(1); + +?> Index: /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/agi-bin/user_login_out.agi =================================================================== --- /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/agi-bin/user_login_out.agi (revision 6326) +++ /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/agi-bin/user_login_out.agi (revision 6326) @@ -0,0 +1,436 @@ +#!/usr/bin/php -q +connect("127.0.0.1", $ampmgruser , $ampmgrpass)) { + exit (1); + } + + $action = get_action(); + $this_device = get_this_device(); + + switch ($action) { + case 'login': + case 'logon': + $new_user = get_login_user(); + debug("Logging in user $new_user to device $this_device"); + user_login($this_device, $new_user); + break; + case 'logout': + debug("Logging out current user from device $this_device"); + user_logout($this_device); + break; + default: + debug("Got unknown action: $action, exiting"); + } + exit; + + /* +exten => s,n,System(rm -f ${ASTSPOOLDIR}/voicemail/device/${CALLERID(number)}) +exten => s,n,System(/bin/ln -s ${ASTSPOOLDIR}/voicemail/${DB(AMPUSER/${AMPUSER}/voicemail)}/${AMPUSER}/ ${ASTSPOOLDIR}/voicemail/device/${CALLERID(number)}) +*/ + + + // Get the requested action (login or logout) + // + function get_action() { + global $argv; + return strtolower(trim($argv['1'])); + //return get_var('ARG1'); + } + + // Get the device passed in (basically CID) + // + function get_this_device() { + global $argv; + return trim($argv['2']); + //return get_var('ARG2'); + } + + // Get the user to login to a device + // + function get_login_user() { + global $argv; + return trim($argv['3']); + //return get_var('ARG3'); + } + + // Login a new user to a device. If there is a current user + // log them out first. + // + function user_login($this_device, $new_user) { + debug("user_login: this_device: $this_device, new_user: $new_user",8); + + $current_user = get_logged_in_user($this_device); + if ($current_user != $new_user) { + if ($current_user != '') { + debug("Logging out current user $current_user from device $this_device so $new_user can login",5); + remove_user($this_device); + } + insert_user($new_user, $this_device); + } else { + debug("User $new_user is already logged into device $this_device",5); + } + } + + // Logout the current user. If device has a default user, log + // them in. + // + function user_logout($this_device) { + debug("user_logout: this_device: $this_device",8); + + $current_user = get_logged_in_user($this_device); + $default_user = get_default_user($this_device); + + debug("current_user: $current_user, default_user: $default_user",8); + + if ($current_user != $default_user) { + remove_user($this_device); + if ($default_user != '') { + debug("Logging in default user $default_user to device $this_device",5); + insert_user($default_user, $this_device); + } + } + } + + // Insert a specificed user into a specified device and then + // update the hint for that user to reflect the new device(s) + // and update the voicemial link + // + function insert_user($user, $device) { + debug("insert_user: user: $user, device: $device",8); + global $agi; + global $astspooldir; + global $fm_devstate; + global $dnd_devstate; + + set_device_user($device, $user); + $previous_devices = get_devices($user); + $new_devices = insert_device($previous_devices,$device); + debug("insert_user: Setting user $user to devices $new_devices",5); + set_user_devices($user, $new_devices); + set_hint($user, $new_devices); + $agi->exec("UserEvent", "\"UserDeviceAdded|Data: {$user},{$device}\""); + + if ($fm_devstate) { + debug("insert_user: Setting FollowMe DEVSTATES for device $device",5); + $agi->set_variable('DEVSTATE(Custom:FOLLOWME'.$device.')',get_followme_state($user)); + } + if ($dnd_devstate) { + debug("insert_user: Setting DND DEVSTATES for device $device",5); + $agi->set_variable('DEVSTATE(Custom:DEVDND'.$device.')',get_dnd_state($user)); + } + + $vmcontext = get_voicemail_context($user); + if ($vmcontext != 'novm') { + exec("/bin/ln -s {$astspooldir}/voicemail/{$vmcontext}/{$user}/ {$astspooldir}/voicemail/device/$device", $output, $ret); + if ($ret) { + debug("Got Return code: $ret trying to: /bin/ln -s {$astspooldir}/voicemail/{$vmcontext}/{$user}/ {$astspooldir}/voicemail/device/$device",5); + } + } + } + + // Remove the current user from a device and then update + // the hint of that current user to reflect their current devices + // + function remove_user($device) { + debug("remove_user: device: $device",8); + global $agi; + global $astspooldir; + global $fm_devstate; + global $dnd_devstate; + + $current_user = get_user($device); + if ($current_user != '') { + $current_devices = get_devices($current_user); + $new_devices = remove_device($current_devices,$device); + debug("remove_user: Setting user $current_user to devices $new_devices",5); + set_user_devices($current_user, $new_devices); + set_hint($current_user, $new_devices); + $agi->exec("UserEvent", "\"UserDeviceRemoved|Data: {$current_user},{$device}\""); + exec("/bin/rm -f {$astspooldir}/voicemail/device/$device",$output, $ret); + + debug("Setting device $device states to INVALID before deleting them",5); + if ($fm_devstate) { + debug("Setting device $device FollowMe state to INVALID before deleting",5); + $agi->set_variable('DEVSTATE(Custom:FOLLOWME'.$device.')','INVALID'); + } + if ($dnd_devstate) { + debug("Setting device $device DND state to INVALID before deleting",5); + $agi->set_variable('DEVSTATE(Custom:DEVDND'.$device.')','INVALID'); + } + // I thought they should then be deleted, but they still end up there probably because setting them to invalid re-creates them + // + //$agi->database_deltree('CustomDevstate','FOLLOWME'.$device); + //$agi->database_deltree('CustomDevstate','DEVDND'.$device); + if ($ret) { + debug("Got Return code: $ret trying to remove: {$astspooldir}/voicemail/device/$device",5); + } + set_device_user($device, 'none'); + } + } + + // Set the hint for a user based on the devices in their AMPUSER object + // + function set_hint($user, $devices) { + debug("set_hint: user: $user, devices: $devices",8); + global $astman; + global $agi; + global $dnd_devstate; + global $intercom_code; + global $ast_version14; + + // If Asterisk version >= 1.4, syntax change is 'dialplan add extension ...' vs. 'add extension ...' + $dp_pre = ($ast_version14) ? 'dialplan ' : ''; + + if ($devices) { + $dial_string = get_dial_string($devices); + if ($dnd_devstate) { + $dial_string .= "&Custom:DND$user"; + } + debug("Setting user $user hint to $dial_string",5); + $response = $astman->send_request('Command',array('Command'=>$dp_pre."add extension {$user},hint,{$dial_string} into ext-local replace")); + if ($intercom_code != 'nointercom' && $intercom_code != '') { + $response = $astman->send_request('Command',array('Command'=>$dp_pre."add extension {$intercom_code}{$user},hint,{$dial_string} into ext-local replace")); + } + } else { + debug("Removing hint for user $user",5); + $response = $astman->send_request('Command',array('Command'=>$dp_pre."remove extension {$user}@ext-local hint")); + if ($intercom_code != 'nointercom' && $intercom_code != '') { + $response = $astman->send_request('Command',array('Command'=>$dp_pre."remove extension {$intercom_code}{$user}@ext-local hint")); + } + } + } + + // Get the actual technology dialstrings from the DEVICE objects (used + // to create proper hints) + // + function get_dial_string($devices) { + debug("get_dial_string: devices: $devices",8); + global $agi; + + $device_array = explode( '&', $devices ); + foreach ($device_array as $adevice) { + $dds = $agi->database_get('DEVICE',$adevice.'/dial'); + $dialstring .= $dds['data'].'&'; + } + return trim($dialstring," &"); + } + + // Insert a new device into a devices string and return the new string + // + function insert_device($devices, $new_device) { + debug("insert_device: devices: $devices, new_device: $new_device",8); + + // We could just append it but this assures no bugs duplicate the device + // + if (trim($new_device) != '') { + $device_arr = explode('&',$devices); + $device_arr[] = $new_device; + $device_arr = array_unique($device_arr); + return implode('&',$device_arr); + } else { + return $devices; + } + } + + // Remove a new device from a devices string and return the new string + // + function remove_device($devices, $remove_device) { + debug("remove_device: devices: $devices, remove_device: $remove_device",8); + + $device_arr = explode('&',$devices); + $device_arr_hash = array_flip($device_arr); + unset($device_arr_hash[$remove_device]); + $new_device_arr = array_flip($device_arr_hash); + $new_devices = implode('&', $new_device_arr); + return $new_devices; + } + + // Get the currently logged in user of a device returning blank + // in no logins + // + function get_logged_in_user($device) { + debug("get_logged_in_user: device: $device",8); + global $agi; + + $user = $agi->database_get('DEVICE',$device.'/user'); + if ($user['result'] == 1 && trim($user['data']) != 'none') { + debug("get_logged_in_user: got user: ".$user['data'],8); + return trim($user['data']); + } + return ''; + } + + // Get the designated default user for the device or blank if none + // + function get_default_user($device) { + debug("get_default_user: device: $device",8); + global $agi; + + $default_user = $agi->database_get('DEVICE',$device.'/default_user'); + if ($default_user['result'] == 1 && trim($default_user['data']) != 'none') { + return trim($default_user['data']); + } + return ''; + } + + // Get the list of current devices for this user + // + function get_devices($user) { + debug("get_devices: user: $user", 8); + global $agi; + + $devices = $agi->database_get('AMPUSER',$user.'/device'); + if ($devices['result'] == 1) { + debug("get_devices: got: ".$devices['data'], 8); + return trim($devices['data']); + } + return ''; + } + + // Get the voicemail context for this user + // + function get_voicemail_context($user) { + debug("get_voicemail_context: user: $user", 8); + global $agi; + + $devices = $agi->database_get('AMPUSER',$user.'/voicemail'); + if ($devices['result'] == 1) { + return trim($devices['data']); + } + return 'novm'; + } + + // Get the user currently associated with this device or blank if none + // + function get_user($device) { + debug("get_user: device: $device", 8); + global $agi; + + $user = $agi->database_get('DEVICE',$device.'/user'); + if ($user['result'] == 1 && trim($user['data']) != 'none') { + debug("get_user: got: ".$user['data'], 8); + return trim($user['data']); + } + debug("get_user: no user found", 8); + return ''; + } + + function get_followme_state($user) { + global $agi; + + $fm_state = $agi->database_get('AMPUSER',$user.'/followme/ddial'); + if ($fm_state['result'] != 1) { + $ret = 'INVALID'; + } else { + switch (trim($fm_state['data'])) { + case 'EXTENSION': + $ret = 'NOT_INUSE'; + break; + case 'DIRECT': + $ret = 'INUSE'; + break; + default: + $ret = 'INVALID'; + } + } + debug("get_followme_state: user $user got state ".$fm_state['data']." returning $ret",8); + return $ret; + } + + function get_dnd_state($user) { + global $agi; + + $dnd_state = $agi->database_get('DND',$user); + if ($dnd_state['result'] != 1) { + $ret = 'NOT_INUSE'; + } else { + if (strtoupper(trim($dnd_state['data'])) == 'YES') { + $ret = 'INUSE'; + } else { + $ret = 'NOT_INUSE'; + } + } + debug("get_dnd_state: user $user got state ".$dnd_state['data']." returning $ret",8); + return $ret; + } + + // Inserts device info into AMPUSER object for specificed user + // + function set_user_devices($user, $devices) { + debug("set_user_devices: user: $user, devices: $devices", 8); + global $agi; + if (empty($devices)) { + debug("No more devices associated with $user, deletting /device key", 8); + $agi->database_del('AMPUSER',$user.'/device'); + } else { + debug("set_user_devices: assigning $devices to $user /device key", 8); + $agi->database_put('AMPUSER',$user.'/device',$devices); + } + } + + // Inserts user into DEVICE object for specified device + // + function set_device_user($device, $user) { + debug("set_device_user: device: $device, user: $user", 8); + global $agi; + $agi->database_put('DEVICE',$device.'/user',$user); + } + + // Get a channel variable + // + function get_var($value) { + global $agi; + + $r = $agi->get_variable( $value ); + if ($r['result'] == 1) { + $result = $r['data']; + return trim($result); + } + return ''; + } + + function debug($string, $level=3) { + global $agi; + $agi->verbose($string, $level); + } Index: /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/agi-bin/enumlookup.agi =================================================================== --- /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/agi-bin/enumlookup.agi (revision 6188) +++ /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/agi-bin/enumlookup.agi (revision 6188) @@ -0,0 +1,146 @@ +#!/usr/bin/php -q + +// Released under Version 2 of the GPL +// Originally written for use with FreePBX. + +// Based on e164.org's enum.php script, available on http://www.e164.org/enum.phps + +/* --------WARNING--------- + * + * This script is auto-copied from an included module and will get overwritten. + * If you modify it, you must change it to write only, in the agi-bin directory, + * to keep it from getting changed. + */ + +require_once "phpagi.php"; + +$AGI = new AGI(); +$lookup = get_var( $AGI, "DIAL_NUMBER" ); + +$enums = Array( + 'e164.org', + //'e164.arpa', + //'e164.info', +); + +// Go through the ENUM providers and look for the number. + +$dialstr = ""; + +foreach ($enums as $enum) { + // Are we using php5 and can use get_dns_record? + if (function_exists("dns_get_record")) { + $arr = get_php5($lookup, $enum); + // else, do we have Pear Net DNS? + // Disabled, as I couldn't easily get it working on my machine + //} elseif ((@include 'Net/DNS.php') =='OK') { + // $arr = get_pear($arpa); + } else { + @exec('dig -h > /dev/null 2>&1 ', $res, $var); + if ($var != 127) { + $arr = get_dig($lookup, $enum); + } else { + $AGI->verbose("ENUM LOOKUPS DISABLED due to php5 not being installed AND no dig command", "0"); + } + } + if (isset($arr[0])) { + foreach($arr as $key => $row) { + $order[$key] = $row["order"]; + $prio[$key] = $row["prio"]; + $nextURI[] = $row['URI']; + $row['URI'] = count($nextURI) - 1; + $arr[$key] = $row; + } + array_multisort($order, SORT_ASC, SORT_NUMERIC, $prio, SORT_ASC, SORT_NUMERIC, $arr); + foreach ($arr as $key => $row) { + if (eregi('SIP|IAX', $row['tech'])) { + $URI = $row['URI']; + $dialstr .= $nextURI[$URI]."|"; + } + } + } +} + +$AGI->verbose("Setting DIALARR to $dialstr", 3); +$AGI->set_variable("DIALARR", $dialstr); + +function get_dig($lookup, $enum) { + global $AGI; + + $AGI->verbose("Looking up $lookup on $enum via shell command DIG",3);; + $arpa = ""; + for ($i = 0; $i < strlen($lookup); $i++) { + $arpa = $lookup[$i].".".$arpa; + } + $lines = trim(`/usr/bin/dig +short ${arpa}${enum} naptr`); + $lines = explode("\n", $lines); + foreach($lines as $line) { + $line = trim($line); + if (preg_match("/^;;/", $line)) + continue; + if (!isset($arr)) $arr = array(); + $line = str_replace("\t", " ", $line); + while(strstr($line, " ")) + $line = str_replace(" ", " ", $line); + $line = str_replace("\"", "", $line); + $line = str_replace("\'", "", $line); + $line = str_replace(" ", "|", $line); + $bits = explode("|", $line); + $bit = explode("!", stripslashes($bits[4])); + $URI = ereg_replace($bit[1], $bit[2], "+".$lookup); + if($URI[3] == ":") + $URI[3] = "/"; + if($URI[4] == ":") + $URI[4] = "/"; + $arr[] = array("order" => $bits[0], "prio" => $bits[1], "tech" => $bits[3], "URI" => $URI); + } + if (isset($arr[0])) { + return $arr; + } else { + return null; + } +} + +function get_php5($lookup, $enum) { + global $AGI; + + $AGI->verbose("Looking up $lookup on $enum via dns_get_record",3); + + $arpa = ""; + for ($i = 0; $i < strlen($lookup); $i++) { + $arpa = $lookup[$i].".".$arpa; + } + $res = dns_get_record("$arpa$enum", DNS_NAPTR); + foreach ($res as $entry) { + if (!isset($arr)) $arr = array(); + $bit = explode("!", $entry['regex']); + $URI = ereg_replace($bit[1], $bit[2], "+".$lookup); + if($URI[3] == ":") $URI[3] = "/"; + if($URI[4] == ":") $URI[4] = "/"; + $arr[] = array("order" => $entry['order'], "prio" => $entry['pref'], "tech" => $entry['services'], "URI" => $URI); + } + if (isset($arr[0])) { + return $arr; + } else { + return null; + } +} + + +// helper functions +function get_var( $agi, $value) +{ + $r = $agi->get_variable( $value ); + + if ($r['result'] == 1) + { + $result = $r['data']; + return $result; + } + else return ''; +} + +?> Index: /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/functions.inc.php =================================================================== --- /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/functions.inc.php (revision 6594) +++ /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/functions.inc.php (revision 6594) @@ -0,0 +1,4729 @@ +generate_sip_general_additional($version); + break; + case 'sip_additional.conf': + return $this->generate_sip_additional($version); + break; + case 'sip_registrations.conf': + return $this->generate_sip_registrations($version); + break; + case 'iax_general_additional.conf': + return $this->generate_iax_general_additional($version); + break; + case 'iax_additional.conf': + return $this->generate_iax_additional($version); + break; + case 'iax_registrations.conf': + return $this->generate_iax_registrations($version); + break; + case 'chan_dahdi_additional.conf': + return $this->generate_zapata_additional($version); + break; + case 'zapata_additional.conf': + return $this->generate_zapata_additional($version); + break; + case 'features_general_additional.conf': + return $this->generate_featuregeneral_additional($version); + break; + case 'features_applicationmap_additional.conf': + return $this->generate_applicationmap_additional($version); + break; + case 'features_featuremap_additional.conf': + return $this->generate_featuremap_additional($version); + break; + } + } + + function addSipGeneral($key, $value) { + $this->_sip_general[] = array('key' => $key, 'value' => $value); + } + + function generate_sip_general_additional($ast_version) { + $output = ''; + + if (isset($this->_sip_general) && is_array($this->_sip_general)) { + foreach ($this->_sip_general as $values) { + $output .= $values['key']."=".$values['value']."\n"; + } + } + return $output; + } + + function addIaxGeneral($key, $value) { + $this->_iax_general[] = array('key' => $key, 'value' => $value); + } + + function generate_iax_general_additional($ast_version) { + $output = ''; + + if (isset($this->_iax_general) && is_array($this->_iax_general)) { + foreach ($this->_iax_general as $values) { + $output .= $values['key']."=".$values['value']."\n"; + } + } + return $output; + } + + function addFeatureGeneral($key, $value) { + $this->_featuregeneral[] = array('key' => $key, 'value' => $value); + } + + function generate_featuregeneral_additional($ast_version) { + $output = ''; + + if (isset($this->_featuregeneral) && is_array($this->_featuregeneral)) { + foreach ($this->_featuregeneral as $values) { + $output .= $values['key']."=".$values['value']."\n"; + } + } + return $output; + } + + function addFeatureMap($key, $value) { + $this->_featuremap[] = array('key' => $key, 'value' => $value); + } + + function generate_featuremap_additional($ast_version) { + $output = ''; + + if (isset($this->_featuremap) && is_array($this->_featuremap)) { + foreach ($this->_featuremap as $values) { + $output .= $values['key']."=".$values['value']."\n"; + } + } + return $output; + } + + function addApplicationMap($key, $value) { + $this->_applicationmap[] = array('key' => $key, 'value' => $value); + } + + function generate_applicationmap_additional($ast_version) { + $output = ''; + + if (isset($this->_applicationmap) && is_array($this->_applicationmap)) { + foreach ($this->_applicationmap as $values) { + $output .= $values['key']."=".$values['value']."\n"; + } + } + return $output; + } + + function generate_sip_additional($ast_version) { + global $db; + + $table_name = "sip"; + $additional = ""; + $output = ""; + + // Asterisk 1.4 requires call-limit be set for hints to work properly + // + if (version_compare($ast_version, "1.4", "ge")) { + $call_limit = "call-limit=50\n"; + $ver12 = false; + } else { + $call_limit = ""; + $ver12 = true; + } + + $sql = "SELECT keyword,data from $table_name where id=-1 and keyword <> 'account' and flags <> 1"; + $results = $db->getAll($sql, DB_FETCHMODE_ASSOC); + if(DB::IsError($results)) { + die($results->getMessage()); + } + foreach ($results as $result) { + if ($ver12) { + $additional .= $result['keyword']."=".$result['data']."\n"; + } else { + $option = $result['data']; + switch (strtolower($result['keyword'])) { + case 'insecure': + if ($option == 'very') + $additional .= "insecure=port,invite\n"; + else if ($option == 'yes') + $additional .= "insecure=port\n"; + else + $additional .= $result['keyword']."=$option\n"; + break; + case 'allow': + case 'disallow': + if ($option != '') + $additional .= $result['keyword']."=$option\n"; + break; + default: + $additional .= $result['keyword']."=$option\n"; + } + } + } + + $sql = "SELECT data,id from $table_name where keyword='account' and flags <> 1 group by data"; + $results = $db->getAll($sql, DB_FETCHMODE_ASSOC); + if(DB::IsError($results)) { + die($results->getMessage()); + } + + foreach ($results as $result) { + $account = $result['data']; + $id = $result['id']; + $output .= "[$account]\n"; + + $sql = "SELECT keyword,data from $table_name where id='$id' and keyword <> 'account' and flags <> 1 order by flags, keyword DESC"; + $results2_pre = $db->getAll($sql, DB_FETCHMODE_ASSOC); + if(DB::IsError($results2_pre)) { + die($results2->getMessage()); + } + + // Move all 'disallow=all' to the top to avoid errors + // + $results2 = array(); + foreach ($results2_pre as $element) { + $options = explode("&", $element['data']); + foreach ($options as $option) { + if ($element['keyword'] == 'disallow' && $option == 'all') { + array_unshift($results2,array('keyword'=>$element['keyword'],'data'=>$option)); + } else { + $results2[] = array('keyword'=>$element['keyword'],'data'=>$option); + } + } + } + unset($results2_pre); + + foreach ($results2 as $result2) { + $option = $result2['data']; + if ($ver12) { + $output .= $result2['keyword']."=$option\n"; + } else { + switch (strtolower($result2['keyword'])) { + case 'insecure': + if ($option == 'very') + $output .= "insecure=port,invite\n"; + else if ($option == 'yes') + $output .= "insecure=port\n"; + else + $output .= $result2['keyword']."=$option\n"; + break; + case 'allow': + case 'disallow': + if ($option != '') + $output .= $result2['keyword']."=$option\n"; + break; + case 'record_in': + case 'record_out': + break; + default: + $output .= $result2['keyword']."=$option\n"; + } + } + } + if ($call_limit && (substr($id,0,4) != "9999" | $id < 99990)) { + + $output .= $call_limit; + } + $output .= $additional."\n"; + } + return $output; + } + + function generate_sip_registrations($ast_version) { + global $db; + + $table_name = "sip"; + $output = ""; + + // items with id like 9999999% get put in registrations file + // + $sql = "SELECT keyword,data from $table_name where id LIKE '9999999%' and keyword <> 'account' and flags <> 1"; + $results = $db->getAll($sql, DB_FETCHMODE_ASSOC); + if(DB::IsError($results)) { + die($results->getMessage()); + } + + foreach ($results as $result) { + $output .= $result['keyword']."=".$result['data']."\n"; + } + + return $output; + } + + function generate_iax_additional($ast_version) { + global $db; + + $table_name = "iax"; + $additional = ""; + $output = ""; + + $ver12 = version_compare($ast_version, '1.4', 'lt'); + + $sql = "SELECT keyword,data from $table_name where id=-1 and keyword <> 'account' and flags <> 1"; + $results = $db->getAll($sql, DB_FETCHMODE_ASSOC); + if(DB::IsError($results)) { + die($results->getMessage()); + } + foreach ($results as $result) { + if ($ver12) { + $additional .= $result['keyword']."=".$result['data']."\n"; + } else { + $option = $result['data']; + switch ($result['keyword']) { + case 'notransfer': + if (strtolower($option) == 'yes') { + $additional .= "transfer=no\n"; + } else if (strtolower($option) == 'no') { + $additional .= "transfer=yes\n"; + } else if (strtolower($option) == 'mediaonly') { + $additional .= "transfer=mediaonly\n"; + } else { + $additional .= $result['keyword']."=$option\n"; + } + break; + case 'allow': + case 'disallow': + if ($option != '') + $additional .= $result['keyword']."=$option\n"; + break; + default: + $additional .= $result['keyword']."=$option\n"; + } + } + } + + $sql = "SELECT data,id from $table_name where keyword='account' and flags <> 1 group by data"; + $results = $db->getAll($sql, DB_FETCHMODE_ASSOC); + if(DB::IsError($results)) { + die($results->getMessage()); + } + + foreach ($results as $result) { + $account = $result['data']; + $id = $result['id']; + $output .= "[$account]\n"; + + $sql = "SELECT keyword,data from $table_name where id='$id' and keyword <> 'account' and flags <> 1 order by flags, keyword DESC"; + $results2_pre = $db->getAll($sql, DB_FETCHMODE_ASSOC); + if(DB::IsError($results2)) { + die($results2_pre->getMessage()); + } + + // Move all 'disallow=all' to the top to avoid errors + // + $results2 = array(); + foreach ($results2_pre as $element) { + $options = explode("&", $element['data']); + foreach ($options as $option) { + if ($element['keyword'] == 'disallow' && $option == 'all') { + array_unshift($results2,array('keyword'=>$element['keyword'],'data'=>$option)); + } else { + $results2[] = array('keyword'=>$element['keyword'],'data'=>$option); + } + } + } + unset($results2_pre); + + foreach ($results2 as $result2) { + $option = $result2['data']; + if ($ver12) { + $output .= $result2['keyword']."=$option\n"; + } else { + switch ($result2['keyword']) { + case 'notransfer': + if (strtolower($option) == 'yes') { + $output .= "transfer=no\n"; + } else if (strtolower($option) == 'no') { + $output .= "transfer=yes\n"; + } else if (strtolower($option) == 'mediaonly') { + $output .= "transfer=mediaonly\n"; + } else { + $output .= $result2['keyword']."=$option\n"; + } + break; + case 'allow': + case 'disallow': + if ($option != '') + $output .= $result2['keyword']."=$option\n"; + break; + case 'record_in': + case 'record_out': + break; + default: + $output .= $result2['keyword']."=$option\n"; + } + } + } + $output .= $additional."\n"; + } + return $output; + } + + function generate_iax_registrations($ast_version) { + global $db; + + $table_name = "iax"; + $output = ""; + + // items with id like 9999999% get put in the registration file + // + $sql = "SELECT keyword,data from $table_name where id LIKE '9999999%' and keyword <> 'account' and flags <> 1"; + $results = $db->getAll($sql, DB_FETCHMODE_ASSOC); + if(DB::IsError($results)) { + die($results->getMessage()); + } + + foreach ($results as $result) { + $output .= $result['keyword']."=".$result['data']."\n"; + } + + return $output; + } + + function generate_zapata_additional($ast_version) { + global $db; + + $table_name = "zap"; + + $additional = ""; + $output = ''; + + $sql = "SELECT keyword,data from $table_name where id=-1 and keyword <> 'account' and flags <> 1"; + $results = $db->getAll($sql, DB_FETCHMODE_ASSOC); + if(DB::IsError($results)) { + die($results->getMessage()); + } + foreach ($results as $result) { + $additional .= $result['keyword']."=".$result['data']."\n"; + } + + $sql = "SELECT data,id from $table_name where keyword='account' and flags <> 1 group by data"; + $results = $db->getAll($sql, DB_FETCHMODE_ASSOC); + if(DB::IsError($results)) { + die($results->getMessage()); + } + + foreach ($results as $result) { + $account = $result['data']; + $id = $result['id']; + $output .= ";;;;;;[$account]\n"; + + $sql = "SELECT keyword,data from $table_name where id=$id and keyword <> 'account' and flags <> 1 order by keyword DESC"; + $results2 = $db->getAll($sql, DB_FETCHMODE_ASSOC); + if(DB::IsError($results2)) { + die($results2->getMessage()); + } + $zapchannel=""; + foreach ($results2 as $result2) { + switch ($result2['keyword']) { + case 'channel': + $zapchannel = $result2['data']; + break; + + // These are not zapata.conf variables so keep out of file + case 'record_out': + case 'record_in': + case 'dial': + break; + default: + $output .= $result2['keyword']."=".$result2['data']."\n"; + } + } + $output .= "channel=>$zapchannel\n"; + $output .= $additional."\n"; + } + return $output; + } +} + +// The destinations this module provides +// returns a associative arrays with keys 'destination' and 'description' +function core_destinations() { + //static destinations + $extens = array(); + $category = 'Terminate Call'; + $extens[] = array('destination' => 'app-blackhole,hangup,1', 'description' => 'Hangup', 'category' => $category); + $extens[] = array('destination' => 'app-blackhole,congestion,1', 'description' => 'Congestion', 'category' => $category); + $extens[] = array('destination' => 'app-blackhole,busy,1', 'description' => 'Busy', 'category' => $category); + $extens[] = array('destination' => 'app-blackhole,zapateller,1', 'description' => 'Play SIT Tone (Zapateller)', 'category' => $category); + $extens[] = array('destination' => 'app-blackhole,musiconhold,1', 'description' => 'Put caller on hold forever', 'category' => $category); + $extens[] = array('destination' => 'app-blackhole,ring,1', 'description' => 'Play ringtones to caller until they hangup', 'category' => $category); + + //get the list of meetmes + $results = core_users_list(); + + if (isset($results) && function_exists('voicemail_getVoicemail')) { + //get voicemail + $uservm = voicemail_getVoicemail(); + $vmcontexts = array_keys($uservm); + foreach ($results as $thisext) { + $extnum = $thisext[0]; + // search vm contexts for this extensions mailbox + foreach ($vmcontexts as $vmcontext) { + if(isset($uservm[$vmcontext][$extnum])){ + //$vmname = $uservm[$vmcontext][$extnum]['name']; + //$vmboxes[$extnum] = array($extnum, '"' . $vmname . '" <' . $extnum . '>'); + $vmboxes[$extnum] = true; + } + } + } + } + + // return an associative array with destination and description + // core provides both users and voicemail boxes as destinations + if (isset($results)) { + foreach($results as $result) { + $extens[] = array('destination' => 'from-did-direct,'.$result['0'].',1', 'description' => ' <'.$result['0'].'> '.$result['1'], 'category' => 'Extensions'); + if(isset($vmboxes[$result['0']])) { + $extens[] = array('destination' => 'ext-local,vmb'.$result['0'].',1', 'description' => '<'.$result[0].'> '.$result[1].' (busy)', 'category' => 'Voicemail'); + $extens[] = array('destination' => 'ext-local,vmu'.$result['0'].',1', 'description' => '<'.$result[0].'> '.$result[1].' (unavail)', 'category' => 'Voicemail'); + $extens[] = array('destination' => 'ext-local,vms'.$result['0'].',1', 'description' => '<'.$result[0].'> '.$result[1].' (no-msg)', 'category' => 'Voicemail'); + } + } + } + + if (isset($extens)) + return $extens; + else + return null; +} + +function core_getdest($exten) { + $dests[] = 'from-did-direct,'.$exten.',1'; + if (!function_exists('voicemail_mailbox_get')) { + return $dests; + } + $box = voicemail_mailbox_get($exten); + if ($box == null) { + return $dests; + } + $dests[] = 'ext-local,vmb'.$exten.',1'; + $dests[] = 'ext-local,vmu'.$exten.',1'; + $dests[] = 'ext-local,vms'.$exten.',1'; + + return $dests; +} + +function core_getdestinfo($dest) { + global $active_modules; + + // Check for Extension Number Destinations + // + if (substr(trim($dest),0,16) == 'from-did-direct,') { + $exten = explode(',',$dest); + $exten = $exten[1]; + $thisexten = core_users_get($exten); + if (empty($thisexten)) { + return array(); + } else { + //$type = isset($active_modules['announcement']['type'])?$active_modules['announcement']['type']:'setup'; + $display = ($amp_conf['AMPEXTENSIONS'] == "deviceanduser")?'users':'extensions'; + return array('description' => 'User Extension '.$exten.': '.$thisexten['name'], + 'edit_url' => "config.php?type=setup&display=$display&extdisplay=".urlencode($exten)."&skip=0", + ); + } + + // Check for voicemail box destinations + // + } else if (substr(trim($dest),0,12) == 'ext-local,vm') { + $exten = explode(',',$dest); + $exten = substr($exten[1],3); + if (!function_exists('voicemail_mailbox_get')) { + return array(); + } + $thisexten = core_users_get($exten); + if (empty($thisexten)) { + return array(); + } + $box = voicemail_mailbox_get($exten); + if ($box == null) { + return array(); + } + $display = ($amp_conf['AMPEXTENSIONS'] == "deviceanduser")?'users':'extensions'; + return array('description' => 'User Extension '.$exten.': '.$thisexten['name'], + 'edit_url' => "config.php?type=setup&display=$display&extdisplay=".urlencode($exten)."&skip=0", + ); + + // Check for blackhole Termination Destinations + // + } else if (substr(trim($dest),0,14) == 'app-blackhole,') { + $exten = explode(',',$dest); + $exten = $exten[1]; + + switch ($exten) { + case 'hangup': + $description = 'Hangup'; + break; + case 'congestion': + $description = 'Congestion'; + break; + case 'busy': + $description = 'Busy'; + break; + case 'zapateller': + $description = 'Play SIT Tone (Zapateller)'; + break; + case 'musiconhold': + $description = 'Put caller on hold forever'; + break; + case 'ring': + $description = 'Play ringtones to caller'; + break; + default: + $description = false; + } + if ($description) { + return array('description' => 'Core: '.$description, + 'edit_url' => false, + ); + } else { + return array(); + } + + // None of the above, so not one of ours + // + } else { + return false; + } +} +/* Generates dialplan for "core" components (extensions & inbound routing) + We call this with retrieve_conf +*/ +function core_get_config($engine) { + global $ext; // is this the best way to pass this? + global $version; // this is not the best way to pass this, this should be passetd together with $engine + global $engineinfo; + global $amp_conf; + global $core_conf; + global $chan_dahdi; + + $modulename = "core"; + + switch($engine) { + case "asterisk": + + // Now add to sip_general_addtional.conf + // + if (isset($core_conf) && is_a($core_conf, "core_conf")) { + $core_conf->addSipGeneral('disallow','all'); + $core_conf->addSipGeneral('allow','ulaw'); + $core_conf->addSipGeneral('allow','alaw'); + $core_conf->addSipGeneral('context','from-sip-external'); + $core_conf->addSipGeneral('callerid','Unknown'); + $core_conf->addSipGeneral('notifyringing','yes'); + if (version_compare($version, '1.4', 'ge')) { + $core_conf->addSipGeneral('notifyhold','yes'); + $core_conf->addSipGeneral('limitonpeers','yes'); + $core_conf->addSipGeneral('tos_sip','cs3'); // Recommended setting from doc/ip-tos.txt + $core_conf->addSipGeneral('tos_audio','ef'); // Recommended setting from doc/ip-tos.txt + $core_conf->addSipGeneral('tos_video','af41'); // Recommended setting from doc/ip-tos.txt + } else { + $core_conf->addSipGeneral('tos','0x68'); // This really doesn't do anything with astersk not running as root + } + $core_conf->addIaxGeneral('disallow','all'); + $core_conf->addIaxGeneral('allow','ulaw'); + $core_conf->addIaxGeneral('allow','alaw'); + $core_conf->addIaxGeneral('allow','gsm'); + $core_conf->addIaxGeneral('mailboxdetail','yes'); + if (version_compare($version, '1.4', 'ge')) { + $core_conf->addIaxGeneral('tos','ef'); // Recommended setting from doc/ip-tos.txt + } + + $fcc = new featurecode($modulename, 'blindxfer'); + $code = $fcc->getCodeActive(); + unset($fcc); + if ($code != '') { + $core_conf->addFeatureMap('blindxfer',$code); + } + + $fcc = new featurecode($modulename, 'atxfer'); + $code = $fcc->getCodeActive(); + unset($fcc); + if ($code != '') { + $core_conf->addFeatureMap('atxfer',$code); + } + + $fcc = new featurecode($modulename, 'automon'); + $code = $fcc->getCodeActive(); + unset($fcc); + if ($code != '') { + $core_conf->addFeatureMap('automon',$code); + } + + $fcc = new featurecode($modulename, 'disconnect'); + $code = $fcc->getCodeActive(); + unset($fcc); + if ($code != '') { + $core_conf->addFeatureMap('disconnect',$code); + } + + $fcc = new featurecode($modulename, 'pickupexten'); + $code = $fcc->getCodeActive(); + unset($fcc); + if ($code != '') { + $core_conf->addFeatureGeneral('pickupexten',$code); + } + } + + // FeatureCodes + $fcc = new featurecode($modulename, 'userlogon'); + $fc_userlogon = $fcc->getCodeActive(); + unset($fcc); + + $fcc = new featurecode($modulename, 'userlogoff'); + $fc_userlogoff = $fcc->getCodeActive(); + unset($fcc); + + $fcc = new featurecode($modulename, 'zapbarge'); + $fc_zapbarge = $fcc->getCodeActive(); + unset($fcc); + + $fcc = new featurecode($modulename, 'chanspy'); + $fc_chanspy = $fcc->getCodeActive(); + unset($fcc); + + $fcc = new featurecode($modulename, 'simu_pstn'); + $fc_simu_pstn = $fcc->getCodeActive(); + unset($fcc); + + $fcc = new featurecode($modulename, 'simu_fax'); + $fc_simu_fax = $fcc->getCodeActive(); + unset($fcc); + + $fcc = new featurecode($modulename, 'pickup'); + $fc_pickup = $fcc->getCodeActive(); + unset($fcc); + + // Log on / off -- all in one context + if ($fc_userlogoff != '' || $fc_userlogon != '') { + $ext->addInclude('from-internal-additional', 'app-userlogonoff'); // Add the include from from-internal + + if ($fc_userlogoff != '') { + $ext->add('app-userlogonoff', $fc_userlogoff, '', new ext_macro('user-logoff')); + $ext->add('app-userlogonoff', $fc_userlogoff, '', new ext_hangup('')); + } + + if ($fc_userlogon != '') { + $ext->add('app-userlogonoff', $fc_userlogon, '', new ext_macro('user-logon')); + $ext->add('app-userlogonoff', $fc_userlogon, '', new ext_hangup('')); + + $clen = strlen($fc_userlogon); + $fc_userlogon = "_$fc_userlogon."; + $ext->add('app-userlogonoff', $fc_userlogon, '', new ext_macro('user-logon,${EXTEN:'.$clen.'}')); + $ext->add('app-userlogonoff', $fc_userlogon, '', new ext_hangup('')); + } + } + + // Call pickup using app_pickup - Note that '**xtn' is hard-coded into the GXPs and SNOMs as a number to dial + // when a user pushes a flashing BLF. + // + // We need to add ringgoups to this so that if an extension is part of a ringgroup, we can try to pickup that + // extension by trying the ringgoup which is what the pickup application is going to respond to. + // + // NOTICE: this may be confusing, we check if this is a BRI build of Asterisk and use dpickup instead of pickup + // if it is. So we simply assign the varaible $ext_pickup which one it is, and use that variable when + // creating all the extnesions below. So those are "$ext_pickup" on purpose! + // + if ($fc_pickup != '') { + $ext->addInclude('from-internal-additional', 'app-pickup'); + $fclen = strlen($fc_pickup); + $ext_pickup = (strstr($engineinfo['raw'], 'BRI')) ? 'ext_dpickup' : 'ext_pickup'; + + $fcc = new featurecode('paging', 'intercom-prefix'); + $intercom_code = $fcc->getCodeActive(); + unset($fcc); + + + $ext->add('app-pickup', "_$fc_pickup.", '', new ext_NoOp('Attempt to Pickup ${EXTEN:'.$fclen.'} by ${CALLERID(num)}')); + $ext->add('app-pickup', "_$fc_pickup.", '', new $ext_pickup('${EXTEN:'.$fclen.'}')); + $ext->add('app-pickup', "_$fc_pickup.", '', new $ext_pickup('${EXTEN:'.$fclen.'}@from-internal')); + $ext->add('app-pickup', "_$fc_pickup.", '', new $ext_pickup('${EXTEN:'.$fclen.'}@from-did-direct')); + $ext->add('app-pickup', "_$fc_pickup.", '', new $ext_pickup('FMPR-${EXTEN:'.$fclen.'}')); + $ext->add('app-pickup', "_$fc_pickup.", '', new $ext_pickup('FMPR-${EXTEN:'.$fclen.'}@from-internal')); + $ext->add('app-pickup', "_$fc_pickup.", '', new $ext_pickup('FMPR-${EXTEN:'.$fclen.'}@from-did-direct')); + if ($intercom_code != '') { + $ext->add('app-pickup', "_{$fc_pickup}{$intercom_code}.", '', new $ext_pickup('${EXTEN:'.strlen($fc_pickup.$intercom_code).'}')); + $ext->add('app-pickup', "_{$fc_pickup}{$intercom_code}.", '', new $ext_pickup('${EXTEN:'.strlen($fc_pickup.$intercom_code).'}@from-internal')); + $ext->add('app-pickup', "_{$fc_pickup}{$intercom_code}.", '', new $ext_pickup('${EXTEN:'.strlen($fc_pickup.$intercom_code).'}@from-did-direct')); + $ext->add('app-pickup', "_{$fc_pickup}{$intercom_code}.", '', new $ext_pickup('FMPR-${EXTEN:'.strlen($fc_pickup.$intercom_code).'}')); + $ext->add('app-pickup', "_{$fc_pickup}{$intercom_code}.", '', new $ext_pickup('FMPR-${EXTEN:'.strlen($fc_pickup.$intercom_code).'}@from-internal')); + $ext->add('app-pickup', "_{$fc_pickup}{$intercom_code}.", '', new $ext_pickup('FMPR-${EXTEN:'.strlen($fc_pickup.$intercom_code).'}@from-did-direct')); + } + $ext->add('app-pickup', "_$fc_pickup.", '', new ext_hangup('')); + // In order to do call pickup in ringgroups, we will need to try the ringgoup number + // when doing call pickup for that ringgoup so we must see who is a member of what ringgroup + // and then generate the dialplan + // + $rg_members = array(); + if (function_exists('ringgroups_list')) { + $rg_list = ringgroups_list(true); + foreach ($rg_list as $item) { + $thisgrp = ringgroups_get($item['grpnum']); + $grpliststr = $thisgrp['grplist']; + $grplist = explode("-", $grpliststr); + foreach ($grplist as $exten) { + if (strpos($exten,"#") === false) { + $rg_members[$exten][] = $item['grpnum']; + } + } + } + } + // Now we have a hash of extensions and what ringgoups they are members of + // so we need to generate the callpickup dialplan for these specific extensions + // to try the ringgoup. + foreach ($rg_members as $exten => $grps) { + $ext->add('app-pickup', "$fc_pickup".$exten, '', new $ext_pickup($exten)); + $ext->add('app-pickup', "$fc_pickup".$exten, '', new $ext_pickup($exten.'@from-internal')); + $ext->add('app-pickup', "$fc_pickup".$exten, '', new $ext_pickup($exten.'@from-did-direct')); + $ext->add('app-pickup', "$fc_pickup".$exten, '', new $ext_pickup('FMPR-'.$exten)); + $ext->add('app-pickup', "$fc_pickup".$exten, '', new $ext_pickup('FMPR-'.$exten.'@from-internal')); + $ext->add('app-pickup', "$fc_pickup".$exten, '', new $ext_pickup('FMPR-'.$exten.'@from-did-direct')); + foreach ($grps as $grp) { + $ext->add('app-pickup', "$fc_pickup".$exten, '', new $ext_pickup($grp.'@from-internal')); + } + $ext->add('app-pickup', "$fc_pickup".$exten, '', new ext_hangup('')); + } + } + + + // zap barge + if ($fc_zapbarge != '') { + $ext->addInclude('from-internal-additional', 'app-zapbarge'); // Add the include from from-internal + + $ext->add('app-zapbarge', $fc_zapbarge, '', new ext_macro('user-callerid')); + $ext->add('app-zapbarge', $fc_zapbarge, '', new ext_setvar('GROUP()','${CALLERID(number)}')); + $ext->add('app-zapbarge', $fc_zapbarge, '', new ext_answer('')); + $ext->add('app-zapbarge', $fc_zapbarge, '', new ext_wait(1)); + $ext->add('app-zapbarge', $fc_zapbarge, '', new ext_zapbarge('')); + $ext->add('app-zapbarge', $fc_zapbarge, '', new ext_hangup('')); + } + + // chan spy + if ($fc_chanspy != '') { + $ext->addInclude('from-internal-additional', 'app-chanspy'); // Add the include from from-internal + $ext->add('app-chanspy', $fc_chanspy, '', new ext_macro('user-callerid')); + $ext->add('app-chanspy', $fc_chanspy, '', new ext_answer('')); + $ext->add('app-chanspy', $fc_chanspy, '', new ext_wait(1)); + $ext->add('app-chanspy', $fc_chanspy, '', new ext_chanspy('')); + $ext->add('app-chanspy', $fc_chanspy, '', new ext_hangup('')); + } + + // Simulate options (ext-test) + if ($fc_simu_pstn != '' || $fc_simu_fax != '') { + $ext->addInclude('from-internal-additional', 'ext-test'); // Add the include from from-internal + + if ($fc_simu_pstn != '') { + if (ctype_digit($fc_simu_pstn)) { + $ext->add('ext-test', $fc_simu_pstn, '', new ext_goto('1', '${EXTEN}', 'from-pstn')); + } else { + $ext->add('ext-test', $fc_simu_pstn, '', new ext_goto('1', 's', 'from-pstn')); + } + } + + if ($fc_simu_fax != '') { + $ext->add('ext-test', $fc_simu_fax, '', new ext_goto('1', 'in_fax', 'ext-fax')); + } + + $ext->add('ext-test', 'h', '', new ext_macro('hangupcall')); + } + + /* Always have Fax detection in ext-did, no matter what */ + + $ext->addInclude('ext-did', 'ext-did-0001'); // Add the include from from-internal + $ext->addInclude('ext-did', 'ext-did-0002'); // Add the include from from-internal + $ext->add('ext-did-0001', 'fax', '', new ext_goto('1','in_fax','ext-fax')); + $ext->add('ext-did-0002', 'fax', '', new ext_goto('1','in_fax','ext-fax')); + $ext->add('ext-did', 'fax', '', new ext_goto('1','in_fax','ext-fax')); + + /* inbound routing extensions */ + $didlist = core_did_list(); + if(is_array($didlist)){ + $catchall = false; + $catchall_context='ext-did-catchall'; + foreach($didlist as $item) { + if (trim($item['destination']) == '') { + continue; + } + $exten = trim($item['extension']); + $cidnum = trim($item['cidnum']); + + // If the user put in just a cid number for routing, we add _. pattern to catch + // all DIDs with that CID number. Asterisk will complain about _. being dangerous + // but we don't want to limit this to just numberic as someone may be trying to + // route a non-numeric did + // + if ($cidnum != '' && $exten == '') { + $exten = '_.'; + $pricid = ($item['pricid']) ? true:false; + } else if (($cidnum != '' && $exten != '') || ($cidnum == '' && $exten == '')) { + $pricid = true; + } else { + $pricid = false; + } + $context = ($pricid) ? "ext-did-0001":"ext-did-0002"; + + $exten = (empty($exten)?"s":$exten); + $exten = $exten.(empty($cidnum)?"":"/".$cidnum); //if a CID num is defined, add it + + $ext->add($context, $exten, '', new ext_setvar('__FROM_DID','${EXTEN}')); + // always set callerID name + $ext->add($context, $exten, '', new ext_gotoif('$[ "${CALLERID(name)}" != "" ] ','cidok')); + $ext->add($context, $exten, '', new ext_setvar('CALLERID(name)','${CALLERID(num)}')); + $ext->add($context, $exten, 'cidok', new ext_noop('CallerID is ${CALLERID(all)}')); + + if (!empty($item['mohclass']) && trim($item['mohclass']) != 'default') { + $ext->add($context, $exten, '', new ext_setmusiconhold($item['mohclass'])); + $ext->add($context, $exten, '', new ext_setvar('__MOHCLASS',$item['mohclass'])); + } + + // If we require RINGING, signal it as soon as we enter. + if ($item['ringing'] === "CHECKED") { + $ext->add($context, $exten, '', new ext_ringing('')); + } + if ($item['delay_answer']) { + $ext->add($context, $exten, '', new ext_wait($item['delay_answer'])); + } + + if ($exten == "s") { + //if the exten is s, then also make a catchall for undefined DIDs + $catchaccount = "_.".(empty($cidnum)?"":"/".$cidnum); + if ($catchaccount =="_." && ! $catchall) { + $catchall = true; + $ext->add($catchall_context, $catchaccount, '', new ext_NoOp('Catch-All DID Match - Found ${EXTEN} - You probably want a DID for this.')); + $ext->add($catchall_context, $catchaccount, '', new ext_goto('1','s','ext-did')); + } + } + + if ($item['faxexten'] != "default") { + $ext->add($context, $exten, '', new ext_setvar('FAX_RX',$item['faxexten'])); + } + if (!empty($item['faxemail'])) { + $ext->add($context, $exten, '', new ext_setvar('FAX_RX_EMAIL',$item['faxemail'])); + } + if ($item['answer'] == "1") { + $ext->add($context, $exten, '', new ext_answer('')); + $ext->add($context, $exten, '', new ext_wait($item['wait'])); + } + if ($item['answer'] == "2") { // NVFaxDetect + $ext->add($context, $exten, '', new ext_answer('')); + $ext->add($context, $exten, '', new ext_playtones('ring')); + $ext->add($context, $exten, '', new ext_nvfaxdetect($item['wait']."|t")); + } + if ($item['privacyman'] == "1") { + $ext->add($context, $exten, '', new ext_macro('privacy-mgr')); + } else { + // if privacymanager is used, this is not necessary as it will not let blocked/anonymous calls through + // otherwise, we need to save the caller presence to set it properly if we forward the call back out the pbx + // note - the indirect table could go away as of 1.4.20 where it is fixed so that SetCallerPres can take + // the raw format. + // + if (version_compare($version, "1.6", "lt")) { + $ext->add($context, $exten, '', new ext_setvar('__CALLINGPRES_SV','${CALLINGPRES_${CALLINGPRES}}')); + } else { + $ext->add($context, $exten, '', new ext_setvar('__CALLINGPRES_SV','${CALLERPRES()}')); + } + $ext->add($context, $exten, '', new ext_setcallerpres('allowed_not_screened')); + } + if (!empty($item['alertinfo'])) { + $ext->add($context, $exten, '', new ext_setvar("__ALERT_INFO", str_replace(';', '\;', $item['alertinfo']))); + } + // Add CID prefix, no need to do checks for existing pre-pends, this is an incoming did so this should + // be the first time the CID is manipulated. We set _RGPREFIX which is the same used throughout the different + // modules. + // + // TODO: If/When RGPREFIX is added to trunks, then see code in ringgroups to strip prefix if added here. + // + // TODO: core FreePBX documentation about this standard. (and probably rename from RGPREFIX to CIDPREFIX) + // + if (!empty($item['grppre'])) { + $ext->add($context, $exten, '', new ext_setvar('_RGPREFIX', $item['grppre'])); + $ext->add($context, $exten, '', new ext_setvar('CALLERID(name)','${RGPREFIX}${CALLERID(name)}')); + } + + //the goto destination + // destination field in 'incoming' database is backwards from what ext_goto expects + $goto_context = strtok($item['destination'],','); + $goto_exten = strtok(','); + $goto_pri = strtok(','); + $ext->add($context, $exten, '', new ext_goto($goto_pri,$goto_exten,$goto_context)); + + } + // If there's not a catchall, make one with an error message + if (!$catchall) { + $ext->add($catchall_context, 's', '', new ext_noop("No DID or CID Match")); + $ext->add($catchall_context, 's', 'a2', new ext_answer('')); + $ext->add($catchall_context, 's', '', new ext_wait('2')); + $ext->add($catchall_context, 's', '', new ext_playback('ss-noservice')); + $ext->add($catchall_context, 's', '', new ext_sayalpha('${FROM_DID}')); + $ext->add($catchall_context, 's', '', new ext_hangup('')); + $ext->add($catchall_context, '_.', '', new ext_setvar('__FROM_DID', '${EXTEN}')); + $ext->add($catchall_context, '_.', '', new ext_noop('Received an unknown call with DID set to ${EXTEN}')); + $ext->add($catchall_context, '_.', '', new ext_goto('a2','s')); + $ext->add($catchall_context, 'h', '', new ext_hangup('')); + } + + } + + // Now create macro-from-zaptel-nnn for each defined channel to route it to the DID routing + // Send it to from-trunk so it is handled as other dids would be handled. + // + foreach (core_zapchandids_list() as $row) { + $channel = $row['channel']; + $did = $row['did']; + + $zap_context = "macro-from-zaptel-{$channel}"; + $ext->add($zap_context, 's', '', new ext_noop('Entering '.$zap_context.' with DID = ${DID} and setting to: '.$did)); + $ext->add($zap_context, 's', '', new ext_setvar('__FROM_DID',$did)); + $ext->add($zap_context, 's', '', new ext_goto('1',$did,'from-trunk')); + } + + /* user extensions */ + $ext->addInclude('from-internal-additional','ext-local'); + + // If running in Dynamic mode, this will insert the hints through an Asterisk #exec call. + // which require "execincludes=yes" to be set in the [options] section of asterisk.conf + // + + $fcc = new featurecode('paging', 'intercom-prefix'); + $intercom_code = $fcc->getCodeActive(); + unset($fcc); + + $intercom_code = ($intercom_code == '') ? 'nointercom' : $intercom_code; + + // Pass the code so agi scripts like user_login_logout know to generate hints + // + $ext->addGlobal('INTERCOMCODE',$intercom_code); + + if ($amp_conf['DYNAMICHINTS']) { + if ($amp_conf['USEDEVSTATE'] && function_exists('donotdisturb_get_config')) { + $add_dnd = ' dnd'; + } else { + $add_dnd = ''; + } + $ext->addExec('ext-local',$amp_conf['AMPBIN'].'/generate_hints.php '.$intercom_code.$add_dnd); + } + $userlist = core_users_list(); + if (is_array($userlist)) { + foreach($userlist as $item) { + $exten = core_users_get($item[0]); + $vm = ((($exten['voicemail'] == "novm") || ($exten['voicemail'] == "disabled") || ($exten['voicemail'] == "")) ? "novm" : $exten['extension']); + + if (isset($exten['ringtimer']) && $exten['ringtimer'] != 0) + $ext->add('ext-local', $exten['extension'], '', new ext_setvar('__RINGTIMER',$exten['ringtimer'])); + + $ext->add('ext-local', $exten['extension'], '', new ext_macro('exten-vm',$vm.",".$exten['extension'])); + + if($vm != "novm") { + $ext->add('ext-local', $exten['extension'], '', new ext_goto('1','vmret')); + $ext->add('ext-local', '${VM_PREFIX}'.$exten['extension'], '', new ext_macro('vm',$vm.',DIRECTDIAL,${IVR_RETVM}')); + $ext->add('ext-local', '${VM_PREFIX}'.$exten['extension'], '', new ext_goto('1','vmret')); + $ext->add('ext-local', 'vmb'.$exten['extension'], '', new ext_macro('vm',$vm.',BUSY,${IVR_RETVM}')); + $ext->add('ext-local', 'vmb'.$exten['extension'], '', new ext_goto('1','vmret')); + $ext->add('ext-local', 'vmu'.$exten['extension'], '', new ext_macro('vm',$vm.',NOANSWER,${IVR_RETVM}')); + $ext->add('ext-local', 'vmu'.$exten['extension'], '', new ext_goto('1','vmret')); + $ext->add('ext-local', 'vms'.$exten['extension'], '', new ext_macro('vm',$vm.',NOMESSAGE,${IVR_RETVM}')); + $ext->add('ext-local', 'vms'.$exten['extension'], '', new ext_goto('1','vmret')); + } else { + // If we return from teh macro, it means we are suppose to return to the IVR + // + $ext->add('ext-local', $exten['extension'], '', new ext_goto('1','return','${IVR_CONTEXT}')); + } + + // Create the hints if running in normal mode + // + if (!$amp_conf['DYNAMICHINTS']) { + $hint = core_hint_get($exten['extension']); + $dnd_string = ($amp_conf['USEDEVSTATE'] && function_exists('donotdisturb_get_config')) ? "&Custom:DND".$exten['extension'] : ''; + if (!empty($hint)) { + $ext->addHint('ext-local', $exten['extension'], $hint.$dnd_string); + if ($intercom_code != '') { + $ext->addHint('ext-local', $intercom_code.$exten['extension'], $hint.$dnd_string); + } + } else if ($dnd_string) { + $ext->addHint('ext-local', $exten['extension'], "&Custom:DND".$exten['extension']); + if ($intercom_code != '') { + $ext->addHint('ext-local', $intercom_code.$exten['extension'], "&Custom:DND".$exten['extension']); + } + } + } + if ($exten['sipname']) { + $ext->add('ext-local', $exten['sipname'], '', new ext_goto('1',$item[0],'from-internal')); + } + // Now make a special context for the IVR inclusions of local extension dialing so that + // when people use the Queues breakout ability, and break out to someone's extensions, voicemail + // works. + // + $ivr_context = 'from-did-direct-ivr'; + $ext->add($ivr_context, $exten['extension'],'', new ext_execif('$["${BLKVM_OVERRIDE}" != ""]','dbDel','${BLKVM_OVERRIDE}')); + $ext->add($ivr_context, $exten['extension'],'', new ext_setvar('__NODEST', '')); + $ext->add($ivr_context, $exten['extension'],'', new ext_goto('1',$exten['extension'],'from-did-direct')); + if($vm != "novm") { + $ext->add($ivr_context, '${VM_PREFIX}'.$exten['extension'],'', new ext_execif('$["${BLKVM_OVERRIDE}" != ""]','dbDel','${BLKVM_OVERRIDE}')); + $ext->add($ivr_context, '${VM_PREFIX}'.$exten['extension'],'', new ext_setvar('__NODEST', '')); + $ext->add($ivr_context, '${VM_PREFIX}'.$exten['extension'],'', new ext_macro('vm',$vm.',DIRECTDIAL,${IVR_RETVM}')); + $ext->add($ivr_context, '${VM_PREFIX}'.$exten['extension'],'', new ext_gotoif('$["${IVR_RETVM}" = "RETURN" & "${IVR_CONTEXT}" != ""]','ext-local,vmret,playret')); + } + } + $ext->add('ext-local', 'vmret', '', new ext_gotoif('$["${IVR_RETVM}" = "RETURN" & "${IVR_CONTEXT}" != ""]','playret')); + $ext->add('ext-local', 'vmret', '', new ext_hangup('')); + $ext->add('ext-local', 'vmret', 'playret', new ext_playback('exited-vm-will-be-transfered&silence/1')); + $ext->add('ext-local', 'vmret', '', new ext_goto('1','return','${IVR_CONTEXT}')); + } + + + // create from-trunk context for each trunk that adds counts to channels + // + $trunklist = core_trunks_list(true); + if (is_array($trunklist)) { + foreach ($trunklist as $trunkprops) { + if (trim($trunkprops['value']) == 'on') { + // value of on is disabled and for zap we don't create a context + continue; + } + switch ($trunkprops['tech']) { + case 'DUNDI': + $macro_name = 'macro-dundi-'.substr($trunkprops['globalvar'],4); + $ext->addSwitch($macro_name,'DUNDI/'.$trunkprops['name']); + $ext->add($macro_name, 's', '', new ext_goto('1','${ARG1}')); + case 'IAX': + case 'IAX2': + case 'SIP': + $trunkgroup = $trunkprops['globalvar']; + $trunkcontext = "from-trunk-".strtolower($trunkprops['tech'])."-".$trunkprops['name']; + $ext->add($trunkcontext, '_.', '', new ext_setvar('GROUP()',$trunkgroup)); + $ext->add($trunkcontext, '_.', '', new ext_goto('1','${EXTEN}','from-trunk')); + break; + default: + } + } + } + + /* dialplan globals */ + // modules should NOT use the globals table to store anything! + // modules should use $ext->addGlobal("testvar","testval"); in their module_get_config() function instead + // I'm cheating for core functionality - do as I say, not as I do ;-) + + // Auto add these globals to give access to agi scripts and other needs, unless defined in the global table. + // + $amp_conf_globals = array( + "ASTETCDIR", + "ASTMODDIR", + "ASTVARLIBDIR", + "ASTAGIDIR", + "ASTSPOOLDIR", + "ASTRUNDIR", + "ASTLOGDIR", + "CWINUSEBUSY", + "AMPMGRUSER", + "AMPMGRPASS" + ); + + $disable_recording = false; + + $sql = "SELECT * FROM globals"; + $globals = sql($sql,"getAll",DB_FETCHMODE_ASSOC); + foreach($globals as $global) { + $value = $global['value']; + if ($chan_dahdi && substr($value, 0, 4) === 'ZAP/') { + $value = 'DAHDI/' . substr($value, 4); + } + $ext->addGlobal($global['variable'],$value); + + // now if for some reason we have a variable in the global table + // that is in our $amp_conf_globals list, then remove it so we + // don't duplicate, the sql table will take precedence + // + if (array_key_exists($global['variable'],$amp_conf_globals)) { + $rm_keys = array_keys($amp_conf_globals,$global['variable']); + foreach ($rm_keys as $index) { + unset($amp_conf_globals[$index]); + } + } + if (($global['variable'] == 'RECORDING_STATE') && (strtoupper($global['value']) == 'DISABLED')) { + $disable_recording = true; + } + } + foreach ($amp_conf_globals as $global) { + if (isset($amp_conf[$global])) { + $value = $amp_conf[$global]; + if ($value === true || $value === false) { + $value = ($value) ? 'true':'false'; + } + $ext->addGlobal($global, $value); + out("Added to globals: $global = $value"); + } + } + // Put the asterisk version in a global for agi etc. + $ext->addGlobal('ASTVERSION', $version); + // Put the use of chan_dahdi in a global for dialparties + $ext->addGlobal('ASTCHANDAHDI', $chan_dahdi ? '1' : '0'); + + // Create CallingPresTable to deal with difference that ${CALINGPRES} returns vs. what + // SetCallerPres() accepts. This is a workaround that gets resolved in 1.6 where + // function CALLINGPRES() is consistent. + // This should be fixed in 1.4.20 but for now we keep it in until 1.6 + // + if (version_compare($version, "1.6", "lt")) { + $ext->addGlobal('CALLINGPRES_0', 'allowed_not_screened'); + $ext->addGlobal('CALLINGPRES_1', 'allowed_passed_screen'); + $ext->addGlobal('CALLINGPRES_2', 'allowed_failed_screen'); + $ext->addGlobal('CALLINGPRES_3', 'allowed'); + $ext->addGlobal('CALLINGPRES_32', 'prohib_not_screened'); + $ext->addGlobal('CALLINGPRES_33', 'prohib_passed_screen'); + $ext->addGlobal('CALLINGPRES_34', 'prohib_failed_screen'); + $ext->addGlobal('CALLINGPRES_35', 'prohib'); + $ext->addGlobal('CALLINGPRES_67', 'unavailable'); + } + + // Let's create globals for each trunk to determine which one's have fixlocalprefix settings. + // this allows us to skip calling the agi script if there are no rules to process saving + // on performance + // + $conf = core_trunks_readDialRulesFile(); + if (is_array($conf)) { + foreach ($conf as $trunknum => $entries) { + $trunkname = substr($trunknum,6); + $ext->addGlobal("PREFIX_TRUNK_$trunkname",count($entries)); + } + } + + // Generate macro-record-enable, if recording is disabled then we just make it a stub + // Otherwise we make it right + // + $context = 'macro-record-enable'; + $exten = 's'; + if ($disable_recording) { + $ext->add($context, $exten, '', new ext_macroexit()); + } else { + $ext->add($context, $exten, '', new ext_gotoif('$["${BLINDTRANSFER}" = ""]', 'check')); + $ext->add($context, $exten, '', new ext_resetcdr('w')); + $ext->add($context, $exten, '', new ext_stopmonitor()); + $ext->add($context, $exten, 'check', new ext_agi('recordingcheck,${STRFTIME(${EPOCH},,%Y%m%d-%H%M%S)},${UNIQUEID}')); + $ext->add($context, $exten, '', new ext_macroexit()); + // keep this 999 in case people have issues updating their recording script + $ext->add($context, $exten, 'record', new ext_mixmonitor('${MIXMON_DIR}${CALLFILENAME}.${MIXMON_FORMAT}','','${MIXMON_POST}'),'1',998); + } + + /* outbound routes */ + // modules should use their own table for storage (and module_get_config() to add dialplan) + // modules should NOT use the extension table to store anything! + $sql = "SELECT application FROM extensions where context = 'outbound-allroutes' ORDER BY application"; + $outrts = sql($sql,"getAll",DB_FETCHMODE_ASSOC); + $ext->addInclude('from-internal-additional','outbound-allroutes'); + $ext->add('outbound-allroutes', 'foo', '', new ext_noop('bar')); + foreach($outrts as $outrt) { + $ext->addInclude('outbound-allroutes',$outrt['application']); + $sql = "SELECT * FROM extensions where context = '".$outrt['application']."' ORDER BY extension, CAST(priority AS UNSIGNED) ASC"; + $thisrt = sql($sql,"getAll",DB_FETCHMODE_ASSOC); + $lastexten = false; + foreach($thisrt as $exten) { + //if emergencyroute, then set channel var + if(strpos($exten['args'],"EMERGENCYROUTE") !== false) + $ext->add($outrt['application'], $exten['extension'], '', new ext_setvar("EMERGENCYROUTE",substr($exten['args'],15))); + if(strpos($exten['args'],"INTRACOMPANYROUTE") !== false) + $ext->add($outrt['application'], $exten['extension'], '', new ext_setvar("INTRACOMPANYROUTE",substr($exten['args'],18))); + // Don't set MOHCLASS if already set, threre may be a feature code that overrode it + if(strpos($exten['args'],"MOHCLASS") !== false) + $ext->add($outrt['application'], $exten['extension'], '', new ext_setvar("MOHCLASS", '${IF($["x${MOHCLASS}"="x"]?'.substr($exten['args'],9).':${MOHCLASS})}' )); + if(strpos($exten['args'],"dialout-trunk") !== false || strpos($exten['args'],"dialout-enum") !== false || strpos($exten['args'],"dialout-dundi") !== false) { + if ($exten['extension'] !== $lastexten) { + + // If NODEST is set, clear it. No point in remembering since dialout-trunk will just end in the + // bit bucket. But if answered by an outside line with transfer capability, we want NODEST to be + // clear so a subsequent transfer to an internal extension works and goes to voicmail or other + // destinations. + // + // Then do one call to user-callerid and record-enable instead of each time as in the past + // + $ext->add($outrt['application'], $exten['extension'], '', new ext_macro('user-callerid,SKIPTTL')); + $ext->add($outrt['application'], $exten['extension'], '', new ext_setvar("_NODEST","")); + $ext->add($outrt['application'], $exten['extension'], '', new ext_macro('record-enable,${AMPUSER},OUT')); + $lastexten = $exten['extension']; + } + $ext->add($outrt['application'], $exten['extension'], '', new ext_macro($exten['args'])); + } + if(strpos($exten['args'],"outisbusy") !== false) + $ext->add($outrt['application'], $exten['extension'], '', new ext_macro("outisbusy")); + } + } + + general_generate_indications(); + + // "blackhole" destinations + $ext->add('app-blackhole', 'hangup', '', new ext_noop('Blackhole Dest: Hangup')); + $ext->add('app-blackhole', 'hangup', '', new ext_hangup()); + + $ext->add('app-blackhole', 'zapateller', '', new ext_noop('Blackhole Dest: Play SIT Tone')); + $ext->add('app-blackhole', 'zapateller', '', new ext_answer()); + $ext->add('app-blackhole', 'zapateller', '', new ext_zapateller()); + // Should hangup ? + // $ext->add('app-blackhole', 'zapateller', '', new ext_hangup()); + + $ext->add('app-blackhole', 'musiconhold', '', new ext_noop('Blackhole Dest: Put caller on hold forever')); + $ext->add('app-blackhole', 'musiconhold', '', new ext_answer()); + $ext->add('app-blackhole', 'musiconhold', '', new ext_musiconhold()); + + $ext->add('app-blackhole', 'congestion', '', new ext_noop('Blackhole Dest: Congestion')); + $ext->add('app-blackhole', 'congestion', '', new ext_answer()); + $ext->add('app-blackhole', 'congestion', '', new ext_playtones('congestion')); + $ext->add('app-blackhole', 'congestion', '', new ext_congestion()); + $ext->add('app-blackhole', 'congestion', '', new ext_hangup()); + + $ext->add('app-blackhole', 'busy', '', new ext_noop('Blackhole Dest: Busy')); + $ext->add('app-blackhole', 'busy', '', new ext_answer()); + $ext->add('app-blackhole', 'busy', '', new ext_playtones('busy')); + $ext->add('app-blackhole', 'busy', '', new ext_busy()); + $ext->add('app-blackhole', 'busy', '', new ext_hangup()); + + $ext->add('app-blackhole', 'ring', '', new ext_noop('Blackhole Dest: Ring')); + $ext->add('app-blackhole', 'ring', '', new ext_answer()); + $ext->add('app-blackhole', 'ring', '', new ext_playtones('ring')); + $ext->add('app-blackhole', 'ring', '', new ext_wait(300)); + $ext->add('app-blackhole', 'ring', '', new ext_hangup()); + + if ($amp_conf['AMPBADNUMBER'] !== false) { + $context = 'bad-number'; + $exten = '_X.'; + $ext->add($context, $exten, '', new extension('ResetCDR()')); + $ext->add($context, $exten, '', new extension('NoCDR()')); + $ext->add($context, $exten, '', new ext_wait('1')); + $ext->add($context, $exten, '', new ext_playback('silence/1&cannot-complete-as-dialed&check-number-dial-again,noanswer')); + $ext->add($context, $exten, '', new ext_wait('1')); + $ext->add($context, $exten, '', new ext_congestion('20')); + $ext->add($context, $exten, '', new ext_hangup()); + } + + /* + ;------------------------------------------------------------------------ + ; [macro-confirm] + ;------------------------------------------------------------------------ + ; CONTEXT: macro-confirm + ; PURPOSE: added default message if none supplied + ; + ; Follom-Me and Ringgroups provide an option to supply a message to be + ; played as part of the confirmation. These changes have added a default + ; message if none is supplied. + ; + ;------------------------------------------------------------------------ + */ + $context = 'macro-confirm'; + $exten = 's'; + + $ext->add($context, $exten, '', new ext_setvar('LOOPCOUNT','0')); + $ext->add($context, $exten, '', new ext_setvar('__MACRO_RESULT','ABORT')); + $ext->add($context, $exten, '', new ext_setvar('MSG1','${IF($["foo${ARG1}" != "foo"]?${ARG1}:"incoming-call-1-accept-2-decline")}')); + if (version_compare($version, "1.4", "ge")) { + $ext->add($context, $exten, 'start', new ext_background('${MSG1},m,${CHANNEL(language)},macro-confirm')); + } else { + $ext->add($context, $exten, 'start', new ext_background('${MSG1},m,${LANGUAGE},macro-confirm')); + } + $ext->add($context, $exten, '', new ext_read('INPUT', '', 1, '', '', 4)); + $ext->add($context, $exten, '', new ext_gotoif('$[${LEN(${INPUT})} > 0]', '${INPUT},1', 't,1')); + + $exten = '1'; + $ext->add($context, $exten, '', new ext_gotoif('$["${DB_EXISTS(RG/${ARG3}/${UNIQCHAN})}" = "0"]', 'toolate,1')); + $ext->add($context, $exten, '', new ext_dbdel('RG/${ARG3}/${UNIQCHAN}')); + $ext->add($context, $exten, '', new ext_dbdel('${BLKVM_OVERRIDE}')); + $ext->add($context, $exten, '', new ext_setvar('__MACRO_RESULT','')); + $ext->add($context, $exten, '', new ext_macroexit()); + + $exten = '2'; + $ext->add($context, $exten, '', new ext_goto(1, 'noanswer')); + + $exten = '3'; + $ext->add($context, $exten, '', new ext_saydigits('${CALLCONFIRMCID}')); + $ext->add($context, $exten, '', new ext_gotoif('$["${DB_EXISTS(RG/${ARG3}/${UNIQCHAN})}" = "0"]', 'toolate,1','s,start')); + + $exten = 't'; + $ext->add($context, $exten, '', new ext_gotoif('$["${DB_EXISTS(RG/${ARG3}/${UNIQCHAN})}" = "0"]', 'toolate,1')); + $ext->add($context, $exten, '', new ext_setvar('LOOPCOUNT','$[ ${LOOPCOUNT} + 1 ]')); + $ext->add($context, $exten, '', new ext_gotoif('$[ ${LOOPCOUNT} < 5 ]', 's,start','noanswer,1')); + + $exten = '_X'; + if (version_compare($version, "1.4", "ge")) { + $ext->add($context, $exten, '', new ext_background('invalid,m,${CHANNEL(language)},macro-confirm')); + } else { + $ext->add($context, $exten, '', new ext_background('invalid,m,${LANGUAGE},macro-confirm')); + } + $ext->add($context, $exten, '', new ext_gotoif('$["${DB_EXISTS(RG/${ARG3}/${UNIQCHAN})}" = "0"]', 'toolate,1')); + $ext->add($context, $exten, '', new ext_setvar('LOOPCOUNT','$[ ${LOOPCOUNT} + 1 ]')); + $ext->add($context, $exten, '', new ext_gotoif('$[ ${LOOPCOUNT} < 5 ]', 's,start','noanswer,1')); + + $exten = 'noanswer'; + $ext->add($context, $exten, '', new ext_setvar('__MACRO_RESULT','ABORT')); + $ext->add($context, $exten, '', new ext_macroexit()); + + $exten = 'toolate'; + $ext->add($context, $exten, '', new ext_setvar('MSG2','${IF($["foo${ARG2}" != "foo"]?${ARG2}:"incoming-call-no-longer-avail")}')); + $ext->add($context, $exten, '', new ext_playback('${MSG2}')); + $ext->add($context, $exten, '', new ext_setvar('__MACRO_RESULT','ABORT')); + $ext->add($context, $exten, '', new ext_macroexit()); + + $exten = 'h'; + $ext->add($context, $exten, '', new ext_macro('hangupcall')); + + + + $context = 'macro-dialout-trunk'; + $exten = 's'; + + /* + * dialout using a trunk, using pattern matching (don't strip any prefix) + * arg1 = trunk number, arg2 = number, arg3 = route password + * + * MODIFIED (PL) + * + * Modified both Dial() commands to include the new TRUNK_OPTIONS from the general + * screen of AMP + */ + $ext->add($context, $exten, '', new ext_set('DIAL_TRUNK', '${ARG1}')); + $ext->add($context, $exten, '', new ext_execif('$[$["${ARG3}" != ""] & $["${DB(AMPUSER/${AMPUSER}/pinless)}" != "NOPASSWD"]]', 'Authenticate', '${ARG3}')); + $ext->add($context, $exten, '', new ext_gotoif('$["x${OUTDISABLE_${DIAL_TRUNK}}" = "xon"]', 'disabletrunk,1')); + $ext->add($context, $exten, '', new ext_set('DIAL_NUMBER', '${ARG2}')); // fixlocalprefix depends on this + $ext->add($context, $exten, '', new ext_set('DIAL_TRUNK_OPTIONS', '${DIAL_OPTIONS}')); // will be reset to TRUNK_OPTIONS if not intra-company + $ext->add($context, $exten, '', new ext_set('OUTBOUND_GROUP', 'OUT_${DIAL_TRUNK}')); + $ext->add($context, $exten, '', new ext_gotoif('$["${OUTMAXCHANS_${DIAL_TRUNK}}foo" = "foo"]', 'nomax')); + $ext->add($context, $exten, '', new ext_gotoif('$[ ${GROUP_COUNT(OUT_${DIAL_TRUNK})} >= ${OUTMAXCHANS_${DIAL_TRUNK}} ]', 'chanfull')); + $ext->add($context, $exten, 'nomax', new ext_gotoif('$["${INTRACOMPANYROUTE}" = "YES"]', 'skipoutcid')); // Set to YES if treated like internal + $ext->add($context, $exten, '', new ext_set('DIAL_TRUNK_OPTIONS', '${TRUNK_OPTIONS}')); + $ext->add($context, $exten, '', new ext_macro('outbound-callerid', '${DIAL_TRUNK}')); + $ext->add($context, $exten, 'skipoutcid', new ext_execif('$["${PREFIX_TRUNK_${DIAL_TRUNK}}" != ""]','AGI','fixlocalprefix')); // this sets DIAL_NUMBER to the proper dial string for this trunk + $ext->add($context, $exten, '', new ext_set('OUTNUM', '${OUTPREFIX_${DIAL_TRUNK}}${DIAL_NUMBER}')); // OUTNUM is the final dial number + $ext->add($context, $exten, '', new ext_set('custom', '${CUT(OUT_${DIAL_TRUNK},:,1)}')); // Custom trunks are prefixed with "AMP:" + + // Back to normal processing, whether intracompany or not. + // But add the macro-setmusic if we don't want music on this outbound call + $ext->add($context, $exten, '', new ext_execif('$[$["${MOHCLASS}" != "default"] & $["${MOHCLASS}" != ""]]', 'Set', 'DIAL_TRUNK_OPTIONS=M(setmusic^${MOHCLASS})${DIAL_TRUNK_OPTIONS}')); + + // This macro call will always be blank and is provided as a hook for customization required prior to making a call + // such as adding SIP header information or other requirements. All the channel variables from above are present + + $ext->add($context, $exten, 'gocall', new ext_macro('dialout-trunk-predial-hook')); + $ext->add($context, $exten, '', new ext_gotoif('$["${PREDIAL_HOOK_RET}" = "BYPASS"]', 'bypass,1')); + + $ext->add($context, $exten, '', new ext_gotoif('$["${custom}" = "AMP"]', 'customtrunk')); + $ext->add($context, $exten, '', new ext_dial('${OUT_${DIAL_TRUNK}}/${OUTNUM}', '300,${DIAL_TRUNK_OPTIONS}')); // Regular Trunk Dial + $ext->add($context, $exten, '', new ext_goto(1, 's-${DIALSTATUS}')); + + $ext->add($context, $exten, 'customtrunk', new ext_set('pre_num', '${CUT(OUT_${DIAL_TRUNK},$,1)}')); + $ext->add($context, $exten, '', new ext_set('the_num', '${CUT(OUT_${DIAL_TRUNK},$,2)}')); // this is where we expect to find string OUTNUM + $ext->add($context, $exten, '', new ext_set('post_num', '${CUT(OUT_${DIAL_TRUNK},$,3)}')); + $ext->add($context, $exten, '', new ext_gotoif('$["${the_num}" = "OUTNUM"]', 'outnum', 'skipoutnum')); // if we didn't find "OUTNUM", then skip to Dial + $ext->add($context, $exten, 'outnum', new ext_set('the_num', '${OUTNUM}')); // replace "OUTNUM" with the actual number to dial + $ext->add($context, $exten, 'skipoutnum', new ext_dial('${pre_num:4}${the_num}${post_num}', '300,${DIAL_TRUNK_OPTIONS}')); + $ext->add($context, $exten, '', new ext_goto(1, 's-${DIALSTATUS}')); + + $ext->add($context, $exten, 'chanfull', new ext_noop('max channels used up')); + + $exten = 's-BUSY'; + $ext->add($context, $exten, '', new ext_noop('Dial failed due to trunk reporting BUSY - giving up')); + $ext->add($context, $exten, '', new ext_playtones('busy')); + $ext->add($context, $exten, '', new ext_busy(20)); + + $exten = 's-NOANSWER'; + $ext->add($context, $exten, '', new ext_noop('Dial failed due to trunk reporting NOANSWER - giving up')); + $ext->add($context, $exten, '', new ext_playtones('congestion')); + $ext->add($context, $exten, '', new ext_congestion(20)); + + $exten = 's-CANCEL'; + $ext->add($context, $exten, '', new ext_noop('Dial failed due to trunk reporting CANCEL - giving up')); + $ext->add($context, $exten, '', new ext_playtones('congestion')); + $ext->add($context, $exten, '', new ext_congestion(20)); + + $exten = 's-CHANUNAVAIL'; + $ext->add($context, $exten, '', new ext_gotoif('$["x${OUTFAIL_${ARG1}}" = "x"]', 'noreport')); + $ext->add($context, $exten, '', new ext_agi('${OUTFAIL_${ARG1}}')); + $ext->add($context, $exten, 'noreport', new ext_noop('TRUNK Dial failed due to ${DIALSTATUS} (hangupcause: ${HANGUPCAUSE}) - failing through to other trunks')); + + $exten = '_s-.'; + $ext->add($context, $exten, '', new ext_gotoif('$["x${OUTFAIL_${ARG1}}" = "x"]', 'noreport')); + $ext->add($context, $exten, '', new ext_agi('${OUTFAIL_${ARG1}}')); + $ext->add($context, $exten, 'noreport', new ext_noop('TRUNK Dial failed due to ${DIALSTATUS} - failing through to other trunks')); + + $ext->add($context, 'disabletrunk', '', new ext_noop('TRUNK: ${OUT_${DIAL_TRUNK}} DISABLED - falling through to next trunk')); + $ext->add($context, 'bypass', '', new ext_noop('TRUNK: ${OUT_${DIAL_TRUNK}} BYPASSING because dialout-trunk-predial-hook')); + + $ext->add($context, 'h', '', new ext_macro('hangupcall')); + + + + + $context = 'macro-dialout-dundi'; + $exten = 's'; + + /* + * Dialout Dundi Trunk + */ + $ext->add($context, $exten, '', new ext_set('DIAL_TRUNK', '${ARG1}')); + $ext->add($context, $exten, '', new ext_execif('$[$["${ARG3}" != ""] & $["${DB(AMPUSER/${AMPUSER}/pinless)}" != "NOPASSWD"]]', 'Authenticate', '${ARG3}')); + $ext->add($context, $exten, '', new ext_gotoif('$["x${OUTDISABLE_${DIAL_TRUNK}}" = "xon"]', 'disabletrunk,1')); + $ext->add($context, $exten, '', new ext_set('DIAL_NUMBER', '${ARG2}')); // fixlocalprefix depends on this + $ext->add($context, $exten, '', new ext_set('DIAL_TRUNK_OPTIONS', '${DIAL_OPTIONS}')); // will be reset to TRUNK_OPTIONS if not intra-company + $ext->add($context, $exten, '', new ext_set('OUTBOUND_GROUP', 'OUT_${DIAL_TRUNK}')); + $ext->add($context, $exten, '', new ext_gotoif('$["${OUTMAXCHANS_${DIAL_TRUNK}}foo" = "foo"]', 'nomax')); + $ext->add($context, $exten, '', new ext_gotoif('$[ ${GROUP_COUNT(OUT_${DIAL_TRUNK})} >= ${OUTMAXCHANS_${DIAL_TRUNK}} ]', 'chanfull')); + $ext->add($context, $exten, 'nomax', new ext_gotoif('$["${INTRACOMPANYROUTE}" = "YES"]', 'skipoutcid')); // Set to YES if treated like internal + $ext->add($context, $exten, '', new ext_set('DIAL_TRUNK_OPTIONS', '${TRUNK_OPTIONS}')); + $ext->add($context, $exten, '', new ext_macro('outbound-callerid', '${DIAL_TRUNK}')); + $ext->add($context, $exten, 'skipoutcid', new ext_execif('$["${PREFIX_TRUNK_${DIAL_TRUNK}}" != ""]','AGI','fixlocalprefix')); // this sets DIAL_NUMBER to the proper dial string for this trunk + $ext->add($context, $exten, '', new ext_set('OUTNUM', '${OUTPREFIX_${DIAL_TRUNK}}${DIAL_NUMBER}')); // OUTNUM is the final dial number + + // Back to normal processing, whether intracompany or not. + // But add the macro-setmusic if we don't want music on this outbound call + $ext->add($context, $exten, '', new ext_execif('$[$["${MOHCLASS}" != "default"] & $["${MOHCLASS}" != ""]]', 'Set', 'DIAL_TRUNK_OPTIONS=M(setmusic^${MOHCLASS})${DIAL_TRUNK_OPTIONS}')); + + // This macro call will always be blank and is provided as a hook for customization required prior to making a call + // such as adding SIP header information or other requirements. All the channel variables from above are present + + $ext->add($context, $exten, 'gocall', new ext_macro('dialout-dundi-predial-hook')); + $ext->add($context, $exten, '', new ext_gotoif('$["${PREDIAL_HOOK_RET}" = "BYPASS"]', 'bypass,1')); + + $ext->add($context, $exten, '', new ext_gotoif('$["${custom}" = "AMP"]', 'customtrunk')); + + $ext->add($context, $exten, '', new ext_macro('dundi-${DIAL_TRUNK}','${OUTNUM}')); + $ext->add($context, $exten, '', new ext_goto(1, 's-${DIALSTATUS}')); + + $ext->add($context, $exten, 'chanfull', new ext_noop('max channels used up')); + + $exten = 's-BUSY'; + $ext->add($context, $exten, '', new ext_noop('Dial failed due to trunk reporting BUSY - giving up')); + $ext->add($context, $exten, '', new ext_playtones('busy')); + $ext->add($context, $exten, '', new ext_busy(20)); + + $exten = 's-NOANSWER'; + $ext->add($context, $exten, '', new ext_noop('Dial failed due to trunk reporting NOANSWER - giving up')); + $ext->add($context, $exten, '', new ext_playtones('congestion')); + $ext->add($context, $exten, '', new ext_congestion(20)); + + $exten = 's-CANCEL'; + $ext->add($context, $exten, '', new ext_noop('Dial failed due to trunk reporting CANCEL - giving up')); + $ext->add($context, $exten, '', new ext_playtones('congestion')); + $ext->add($context, $exten, '', new ext_congestion(20)); + + $exten = '_s-.'; + $ext->add($context, $exten, '', new ext_gotoif('$["x${OUTFAIL_${ARG1}}" = "x"]', 'noreport')); + $ext->add($context, $exten, '', new ext_agi('${OUTFAIL_${ARG1}}')); + $ext->add($context, $exten, 'noreport', new ext_noop('TRUNK Dial failed due to ${DIALSTATUS} - failing through to other trunks')); + + $ext->add($context, 'disabletrunk', '', new ext_noop('TRUNK: ${OUT_${DIAL_TRUNK}} DISABLED - falling through to next trunk')); + $ext->add($context, 'bypass', '', new ext_noop('TRUNK: ${OUT_${DIAL_TRUNK}} BYPASSING because dialout-dundi-predial-hook')); + + $ext->add($context, 'h', '', new ext_macro('hangupcall')); + + + + /* + * sets the callerid of the device to that of the logged in user + * + * ${AMPUSER} is set upon return to the real user despite any aliasing that may + * have been set as a result of the AMPUSER//cidnum field. This is used by + * features like DND, CF, etc. to set the proper structure on aliased instructions + */ + $context = 'macro-user-callerid'; + $exten = 's'; + + //$ext->add($context, $exten, '', new ext_noop('user-callerid: ${CALLERID(name)} ${CALLERID(number)}')); + + // make sure AMPUSER is set if it doesn't get set below + $ext->add($context, $exten, '', new ext_set('AMPUSER', '${IF($["foo${AMPUSER}" = "foo"]?${CALLERID(number)}:${AMPUSER})}')); + $ext->add($context, $exten, '', new ext_gotoif('$["${CHANNEL:0:5}" = "Local"]', 'report')); + $ext->add($context, $exten, '', new ext_execif('$["${REALCALLERIDNUM:1:2}" = ""]', 'Set', 'REALCALLERIDNUM=${CALLERID(number)}')); + //$ext->add($context, $exten, 'start', new ext_noop('REALCALLERIDNUM is ${REALCALLERIDNUM}')); + $ext->add($context, $exten, '', new ext_set('AMPUSER', '${DB(DEVICE/${REALCALLERIDNUM}/user)}')); + $ext->add($context, $exten, '', new ext_set('AMPUSERCIDNAME', '${DB(AMPUSER/${AMPUSER}/cidname)}')); + $ext->add($context, $exten, '', new ext_gotoif('$["x${AMPUSERCIDNAME:1:2}" = "x"]', 'report')); + + // user may masquerade as a different user internally, so set the internal cid as indicated + // but keep the REALCALLERID which is used to determine their true identify and lookup info + // during outbound calls. + $ext->add($context, $exten, '', new ext_set('AMPUSERCID', '${IF($["${DB_EXISTS(AMPUSER/${AMPUSER}/cidnum)}" = "1"]?${DB_RESULT}:${AMPUSER})}')); + $ext->add($context, $exten, '', new ext_set('CALLERID(all)', '"${AMPUSERCIDNAME}" <${AMPUSERCID}>')); + $ext->add($context, $exten, '', new ext_set('REALCALLERIDNUM', '${DB(DEVICE/${REALCALLERIDNUM}/user)}')); + /* + * This is where to splice in things like setting the language based on a user's astdb setting, + * or where you might set the CID account code based on a user instead of the device settings. + */ + $ext->add($context, $exten, 'report', new ext_gotoif('$[ "${ARG1}" = "SKIPTTL" ]', 'continue')); + $ext->add($context, $exten, 'report2', new ext_set('__TTL', '${IF($["foo${TTL}" = "foo"]?64:$[ ${TTL} - 1 ])}')); + $ext->add($context, $exten, '', new ext_gotoif('$[ ${TTL} > 0 ]', 'continue')); + $ext->add($context, $exten, '', new ext_wait('${RINGTIMER}')); // wait for a while, to give it a chance to be picked up by voicemail + $ext->add($context, $exten, '', new ext_answer()); + $ext->add($context, $exten, '', new ext_wait('2')); + $ext->add($context, $exten, '', new ext_playback('im-sorry&an-error-has-occured&with&call-forwarding')); + $ext->add($context, $exten, '', new ext_macro('hangupcall')); + $ext->add($context, $exten, '', new ext_congestion(20)); + $ext->add($context, $exten, 'continue', new ext_noop('Using CallerID ${CALLERID(all)}')); + $ext->add($context, 'h', '', new ext_macro('hangupcall')); + + /* + * arg1 = trunk number, arg2 = number + * + * Re-written to use enumlookup.agi + */ + + $context = 'macro-dialout-enum'; + $exten = 's'; + + $ext->add($context, $exten, '', new ext_execif('$[$["${ARG3}" != ""] & $["${DB(AMPUSER/${AMPUSER}/pinless)}" != "NOPASSWD"]]', 'Authenticate', '${ARG3}')); + $ext->add($context, $exten, '', new ext_macro('outbound-callerid', '${ARG1}')); + $ext->add($context, $exten, '', new ext_set('OUTBOUND_GROUP', 'OUT_${ARG1}')); + $ext->add($context, $exten, '', new ext_gotoif('$["${OUTMAXCHANS_${ARG1}}foo" = "foo"]', 'nomax')); + $ext->add($context, $exten, '', new ext_gotoif('$[ ${GROUP_COUNT(OUT_${ARG1})} >= ${OUTMAXCHANS_${ARG1}} ]', 'nochans')); + $ext->add($context, $exten, 'nomax', new ext_set('DIAL_NUMBER', '${ARG2}')); + $ext->add($context, $exten, '', new ext_set('DIAL_TRUNK', '${ARG1}')); + $ext->add($context, $exten, '', new ext_execif('$["${PREFIX_TRUNK_${DIAL_TRUNK}}" != ""]','AGI','fixlocalprefix')); // this sets DIAL_NUMBER to the proper dial string for this trunk + // Replacement for asterisk's ENUMLOOKUP function + $ext->add($context, $exten, '', new ext_agi('enumlookup.agi')); + // Now we have the variable DIALARR set to a list of URI's that can be called, in order of priority + // Loop through them trying them in order. + $ext->add($context, $exten, 'dialloop', new ext_gotoif('$["foo${DIALARR}"="foo"]', 'end')); + $ext->add($context, $exten, '', new ext_set('TRYDIAL', '${CUT(DIALARR,%,1)}')); + $ext->add($context, $exten, '', new ext_set('DIALARR', '${CUT(DIALARR,%,2-)}')); + $ext->add($context, $exten, '', new ext_dial('${TRYDIAL}', '')); + $ext->add($context, $exten, '', new ext_noop('Dial exited in macro-enum-dialout with ${DIALSTATUS}')); + // Now, if we're still here, that means the Dial failed for some reason. + // If it's CONGESTION or CHANUNAVAIL we want to try again on a different + // different channel. If there's no more left, the dialloop tag will exit. + $ext->add($context, $exten, '', new ext_gotoif('$[ $[ "${DIALSTATUS}" = "CHANUNAVAIL" ] | $[ "${DIALSTATUS}" = "CONGESTION" ] ]', 'dialloop')); + // If we're here, then it's BUSY or NOANSWER or something and well, deal with it. + $ext->add($context, $exten, 'dialfailed', new ext_goto(1, 's-${DIALSTATUS}')); + // Here are the exit points for the macro. + $ext->add($context, $exten, 'nochans', new ext_noop('max channels used up')); + $ext->add($context, $exten, 'end', new ext_noop('Exiting macro-dialout-enum')); + $ext->add($context, 's-BUSY', '', new ext_noop('Trunk is reporting BUSY')); + $ext->add($context, 's-BUSY', '', new ext_busy(20)); + $ext->add($context, '_s-.', '', new ext_noop('Dial failed due to ${DIALSTATUS}')); + + /* + * overrides callerid out trunks + * arg1 is trunk + * macro-user-callerid should be called _before_ using this macro + */ + + $context = 'macro-outbound-callerid'; + $exten = 's'; + + // If we modified the caller presence, set it back. This allows anonymous calls to be internally prepended but keep + // their status if forwarded back out. Not doing this can result in the trunk CID being displayed vs. 'blocked call' + // + if (version_compare($version, "1.6", "lt")) { + $ext->add($context, $exten, '', new ext_execif('$["${CALLINGPRES_SV}" != ""]', 'SetCallerPres', '${CALLINGPRES_SV}')); + } else { + $ext->add($context, $exten, '', new ext_execif('$["${CALLINGPRES_SV}" != ""]', 'Set', 'CALLERPRES()=${CALLINGPRES_SV}')); + } + + // Keep the original CallerID number, for failover to the next trunk. + + $ext->add($context, $exten, '', new ext_execif('$["${REALCALLERIDNUM:1:2}" = ""]', 'Set', 'REALCALLERIDNUM=${CALLERID(number)}')); + // If this came through a ringgroup or CF, then we want to retain original CID unless + // OUTKEEPCID_${trunknum} is set. + // Save then CIDNAME while it is still intact in case we end up sending out this same CID + $ext->add($context, $exten, 'start', new ext_gotoif('$[ $["${REALCALLERIDNUM}" = ""] | $["${KEEPCID}" != "TRUE"] | $["${OUTKEEPCID_${ARG1}}" = "on"] ]', 'normcid')); // Set to TRUE if coming from ringgroups, CF, etc. + $ext->add($context, $exten, '', new ext_set('USEROUTCID', '${REALCALLERIDNUM}')); + //$ext->add($context, $exten, '', new ext_set('REALCALLERIDNAME', '${CALLERID(name)}')); + + // We now have to make sure the CID is valid. If we find an AMPUSER with the same CID, we assume it is an internal + // call (would be quite a conincidence if not) and go through the normal processing to get that CID. If a device + // is set for this CID, then it must be internal + // If we end up using USEROUTCID at the end, it may still be the REALCALLERIDNUM we saved above. That is determined + // if the two are equal, AND there is no CALLERID(name) present since it has been removed by the CALLERID(all)=${USEROUTCID} + // setting. If this is the case, then we put the orignal name back in to send out. Although the CNAME is not honored by most + // carriers, there are cases where it is so this preserves that information to be used by those carriers who do honor it. + $ext->add($context, $exten, '', new ext_gotoif('$["foo${DB(AMPUSER/${REALCALLERIDNUM}/device)}" = "foo"]', 'bypass')); + + $ext->add($context, $exten, 'normcid', new ext_set('USEROUTCID', '${DB(AMPUSER/${REALCALLERIDNUM}/outboundcid)}')); + $ext->add($context, $exten, 'bypass', new ext_set('EMERGENCYCID', '${DB(DEVICE/${REALCALLERIDNUM}/emergency_cid)}')); + $ext->add($context, $exten, '', new ext_set('TRUNKOUTCID', '${OUTCID_${ARG1}}')); + $ext->add($context, $exten, '', new ext_gotoif('$[ $["${EMERGENCYROUTE:1:2}" = ""] | $["${EMERGENCYCID:1:2}" = ""] ]', 'trunkcid')); // check EMERGENCY ROUTE + $ext->add($context, $exten, '', new ext_set('CALLERID(all)', '${EMERGENCYCID}')); // emergency cid for device + $ext->add($context, $exten, 'exit', new ext_macroexit()); + + + $ext->add($context, $exten, 'trunkcid', new ext_execif('$["${TRUNKOUTCID:1:2}" != ""]', 'Set', 'CALLERID(all)=${TRUNKOUTCID}')); + + $ext->add($context, $exten, 'usercid', new ext_gotoif('$["${USEROUTCID:1:2}" = ""]', 'exit')); // check CID override for extension + $ext->add($context, $exten, '', new ext_set('CALLERID(all)', '${USEROUTCID}')); + //$ext->add($context, $exten, '', new ext_gotoif('$["x${CALLERID(name)}"!="xhidden"]', 'checkname', 'hidecid')); // check CID blocking for extension + if (version_compare($version, "1.6", "lt")) { + $ext->add($context, $exten, 'hidecid', new ext_execif('$["${CALLERID(name)}"="hidden"]', 'SetCallerPres', 'prohib_passed_screen')); + } else { + $ext->add($context, $exten, 'hidecid', new ext_execif('$["${CALLERID(name)}"="hidden"]', 'Set', 'CALLERPRES()=prohib_passed_screen')); + } + //$ext->add($context, $exten, 'checkname', new ext_execif('$[ $[ "${CALLERID(number)}" = "${REALCALLERIDNUM}" ] & $[ "${CALLERID(name)}" = "" ] ]', 'Set', 'CALLERID(name)=${REALCALLERIDNAME}')); + + + /* + * Adds a dynamic agent/member to a Queue + * Prompts for call-back number - in not entered, uses CIDNum + */ + + $context = 'macro-agent-add'; + $exten = 's'; + + $ext->add($context, $exten, '', new ext_wait(1)); + $ext->add($context, $exten, '', new ext_macro('user-callerid', 'SKIPTTL')); + $ext->add($context, $exten, 'a3', new ext_read('CALLBACKNUM', 'agent-login')); // get callback number from user + $ext->add($context, $exten, '', new ext_gotoif('$["${CALLBACKNUM}" != ""]', 'a7')); // if user just pressed # or timed out, use cidnum + $ext->add($context, $exten, 'a5', new ext_set('CALLBACKNUM', '${AMPUSER}')); + $ext->add($context, $exten, '', new ext_execif('$["${CALLBACKNUM}" = ""]', 'Set', 'CALLBACKNUM=${CALLERID(number)}')); + $ext->add($context, $exten, '', new ext_gotoif('$["${CALLBACKNUM}" = ""]', 'a3')); // if still no number, start over + $ext->add($context, $exten, 'a7', new ext_gotoif('$["${CALLBACKNUM}" = "${ARG1}"]', 'invalid')); // Error, they put in the queue number + $ext->add($context, $exten, '', new ext_execif('$["${QREGEX}" != ""]', 'GotoIf', '$["${REGEX("${QREGEX}" ${CALLBACKNUM})}" = "0"]?invalid')); + $ext->add($context, $exten, '', new ext_execif('$["${ARG2}" != ""]', 'Authenticate', '${ARG2}')); + $ext->add($context, $exten, 'a9', new ext_addqueuemember('${ARG1}', 'Local/${CALLBACKNUM}@from-internal/n')); // using chan_local allows us to have agents over trunks + $ext->add($context, $exten, '', new ext_userevent('Agentlogin', 'Agent: ${CALLBACKNUM}')); + $ext->add($context, $exten, '', new ext_wait(1)); + $ext->add($context, $exten, '', new ext_playback('agent-loginok&with&extension')); + $ext->add($context, $exten, '', new ext_saydigits('${CALLBACKNUM}')); + $ext->add($context, $exten, '', new ext_hangup()); + $ext->add($context, $exten, '', new ext_macroexit()); + $ext->add($context, $exten, 'invalid', new ext_playback('pbx-invalid')); + $ext->add($context, $exten, '', new ext_goto('a3')); + + /* + * Removes a dynamic agent/member from a Queue + * Prompts for call-back number - in not entered, uses CIDNum + */ + + $context = 'macro-agent-del'; + + $ext->add($context, $exten, '', new ext_wait(1)); + $ext->add($context, $exten, '', new ext_macro('user-callerid', 'SKIPTTL')); + $ext->add($context, $exten, 'a3', new ext_read('CALLBACKNUM', 'agent-logoff')); // get callback number from user + $ext->add($context, $exten, '', new ext_gotoif('$["${CALLBACKNUM}" = ""]', 'a5', 'a7')); // if user just pressed # or timed out, use cidnum + $ext->add($context, $exten, 'a5', new ext_set('CALLBACKNUM', '${AMPUSER}')); + $ext->add($context, $exten, '', new ext_execif('$["${CALLBACKNUM}" = ""]', 'Set', 'CALLBACKNUM=${CALLERID(number)}')); + $ext->add($context, $exten, '', new ext_gotoif('$["${CALLBACKNUM}" = ""]', 'a3')); // if still no number, start over + $ext->add($context, $exten, 'a7', new ext_removequeuemember('${ARG1}', 'Local/${CALLBACKNUM}@from-internal/n')); + $ext->add($context, $exten, '', new ext_userevent('RefreshQueue')); + $ext->add($context, $exten, '', new ext_wait(1)); + $ext->add($context, $exten, '', new ext_playback('agent-loggedoff')); + $ext->add($context, $exten, '', new ext_hangup()); + + $context = 'macro-systemrecording'; + + //exten => s,1,Set(RECFILE=${IF($["${ARG2}" = ""]?/tmp/${AMPUSER}-ivrrecording:${ARG2})}) + $ext->add($context, 's', '', new ext_setvar('RECFILE','${IF($["${ARG2}" = ""]?/tmp/${AMPUSER}-ivrrecording:${ARG2})}')); + $ext->add($context, 's', '', new ext_execif('$["${ARG3}" != ""]','Authenticate','${ARG3}')); + $ext->add($context, 's', '', new ext_goto(1, '${ARG1}')); + + $exten = 'dorecord'; + + // Delete all versions of the current sound file (does not consider languages though + // otherwise you might have some versions that are not re-recorded + // + $ext->add($context, $exten, '', new ext_system('rm ${ASTVARLIBDIR}/sounds/${RECFILE}.*')); + $ext->add($context, $exten, '', new ext_record('${RECFILE}:wav')); + $ext->add($context, $exten, '', new ext_wait(1)); + $ext->add($context, $exten, '', new ext_goto(1, 'confmenu')); + + $exten = 'docheck'; + + $ext->add($context, $exten, '', new ext_playback('beep')); + if (version_compare($version, "1.4", "ge")) { + $ext->add($context, $exten, 'dc_start', new ext_background('${RECFILE},m,${CHANNEL(language)},macro-systemrecording')); + } else { + $ext->add($context, $exten, 'dc_start', new ext_background('${RECFILE},m,${LANGUAGE},macro-systemrecording')); + } + $ext->add($context, $exten, '', new ext_wait(1)); + $ext->add($context, $exten, '', new ext_goto(1, 'confmenu')); + + $exten = 'confmenu'; + if (version_compare($version, "1.4", "ge")) { + $ext->add($context, $exten, '', new ext_background('to-listen-to-it&press-1&to-rerecord-it&press-star&astcc-followed-by-pound,m,${CHANNEL(language)},macro-systemrecording')); + } else { + $ext->add($context, $exten, '', new ext_background('to-listen-to-it&press-1&to-rerecord-it&press-star&astcc-followed-by-pound,m,${LANGUAGE},macro-systemrecording')); + } + $ext->add($context, $exten, '', new ext_read('RECRESULT', '', 1, '', '', 4)); + $ext->add($context, $exten, '', new ext_gotoif('$["x${RECRESULT}"="x*"]', 'dorecord,1')); + $ext->add($context, $exten, '', new ext_gotoif('$["x${RECRESULT}"="x1"]', 'docheck,2')); + $ext->add($context, $exten, '', new ext_goto(1)); + + $ext->add($context, '1', '', new ext_goto('dc_start', 'docheck')); + $ext->add($context, '*', '', new ext_goto(1, 'dorecord')); + + $ext->add($context, 't', '', new ext_playback('goodbye')); + $ext->add($context, 't', '', new ext_hangup()); + + $ext->add($context, 'i', '', new ext_playback('pm-invalid-option')); + $ext->add($context, 'i', '', new ext_goto(1, 'confmenu')); + + $ext->add($context, 'h', '', new ext_hangup()); + + + $context = 'from-zaptel'; + $exten = '_X.'; + + $ext->add($context, $exten, '', new ext_set('DID', '${EXTEN}')); + $ext->add($context, $exten, '', new ext_goto(1, 's')); + + $exten = 's'; + $ext->add($context, $exten, '', new ext_noop('Entering from-zaptel with DID == ${DID}')); + // Some trunks _require_ a RINGING be sent before an Answer. + $ext->add($context, $exten, '', new ext_ringing()); + // If ($did == "") { $did = "s"; } + $ext->add($context, $exten, '', new ext_set('DID', '${IF($["${DID}"= ""]?s:${DID})}')); + $ext->add($context, $exten, '', new ext_noop('DID is now ${DID}')); + if ($chan_dahdi) { + $ext->add($context, $exten, '', new ext_gotoif('$["${CHANNEL:0:5}"="DAHDI"]', 'zapok', 'notzap')); + } else { + $ext->add($context, $exten, '', new ext_gotoif('$["${CHANNEL:0:3}"="Zap"]', 'zapok', 'notzap')); + } + $ext->add($context, $exten, 'notzap', new ext_goto('1', '${DID}', 'from-pstn')); + // If there's no ext-did,s,1, that means there's not a no did/no cid route. Hangup. + $ext->add($context, $exten, '', new ext_macro('Hangupcall', 'dummy')); + $ext->add($context, $exten, 'zapok', new ext_noop('Is a Zaptel Channel')); + if ($chan_dahdi) { + $ext->add($context, $exten, '', new ext_set('CHAN', '${CHANNEL:6}')); + } else { + $ext->add($context, $exten, '', new ext_set('CHAN', '${CHANNEL:4}')); + } + $ext->add($context, $exten, '', new ext_set('CHAN', '${CUT(CHAN,-,1)}')); + $ext->add($context, $exten, '', new ext_macro('from-zaptel-${CHAN}', '${DID},1')); + // If nothing there, then treat it as a DID + $ext->add($context, $exten, '', new ext_noop('Returned from Macro from-zaptel-${CHAN}')); + $ext->add($context, $exten, '', new ext_goto(1, '${DID}', 'from-pstn')); + $ext->add($context, 'fax', '', new ext_goto(1, 'in_fax', 'ext-fax')); + + break; + } +} + +/* begin page.ampusers.php functions */ + +function core_ampusers_add($username, $password, $extension_low, $extension_high, $deptname, $sections) { + $sql = "INSERT INTO ampusers (username, password, extension_low, extension_high, deptname, sections) VALUES ("; + $sql .= "'".$username."',"; + $sql .= "'".$password."',"; + $sql .= "'".$extension_low."',"; + $sql .= "'".$extension_high."',"; + $sql .= "'".$deptname."',"; + $sql .= "'".implode(";",$sections)."');"; + + sql($sql,"query"); +} + +function core_ampusers_del($username) { + $sql = "DELETE FROM ampusers WHERE username = '".$username."'"; + sql($sql,"query"); +} + +function core_ampusers_list() { + $sql = "SELECT username FROM ampusers ORDER BY username"; + return sql($sql,"getAll"); +} + +/* end page.ampusers.php functions */ + + + + + + + + + +/* begin page.did.php functions */ + +function core_did_list($order='extension'){ + switch ($order) { + case 'description': + $sql = "SELECT * FROM incoming ORDER BY description,extension,cidnum"; + break; + case 'extension': + default: + $sql = "SELECT * FROM incoming ORDER BY extension,cidnum"; + } + return sql($sql,"getAll",DB_FETCHMODE_ASSOC); +} + +function core_did_get($extension="",$cidnum=""){ + $sql = "SELECT * FROM incoming WHERE cidnum = \"$cidnum\" AND extension = \"$extension\""; + return sql($sql,"getRow",DB_FETCHMODE_ASSOC); +} + +function core_did_del($extension,$cidnum){ + $sql="DELETE FROM incoming WHERE cidnum = \"$cidnum\" AND extension = \"$extension\""; + sql($sql); +} + +function core_did_edit($old_extension,$old_cidnum, $incoming){ + global $db; + + $old_extension = $db->escapeSimple(trim($old_extension)); + $old_cidnum = $db->escapeSimple(trim($old_cidnum)); + + $incoming['extension'] = trim($incoming['extension']); + $incoming['cidnum'] = trim($incoming['cidnum']); + + $extension = $db->escapeSimple($incoming['extension']); + $cidnum = $db->escapeSimple($incoming['cidnum']); + + // if did or cid changed, then check to make sure that this pair is not already being used. + // + if (($extension != $old_extension) || ($cidnum != $old_cidnum)) { + $existing=core_did_get($extension,$cidnum); + } + + if (empty($existing)) { + core_did_del($old_extension,$old_cidnum); + core_did_add($incoming); + return true; + } else { + echo ""; + } + return false; +} + +function core_did_add($incoming,$target=false){ + global $db; + foreach ($incoming as $key => $val) { ${$key} = $db->escapeSimple($val); } // create variables from request + + // Check to make sure the did is not being used elsewhere + // + $existing=core_did_get($extension,$cidnum); + + if (empty($existing)) { + $destination= ($target) ? $target : ${$goto0.'0'}; + $sql="INSERT INTO incoming (cidnum,extension,destination,faxexten,faxemail,answer,wait,privacyman,alertinfo, ringing, mohclass, description, grppre, delay_answer, pricid) values ('$cidnum','$extension','$destination','$faxexten','$faxemail','$answer','$wait','$privacyman','$alertinfo', '$ringing', '$mohclass', '$description', '$grppre', '$delay_answer', '$pricid')"; + sql($sql); + return true; + } else { + echo ""; + return false; + } +} + +/* end page.did.php functions */ + + +/* begin page.devices.php functions */ + +//get the existing devices +function core_devices_list($tech="all",$detail=false,$get_all=false) { + if (strtolower($detail) == 'full') { + $sql = "SELECT * FROM devices"; + } else { + $sql = "SELECT id,description FROM devices"; + } + switch (strtoupper($tech)) { + case "IAX": + $sql .= " WHERE tech = 'iax2'"; + break; + case "IAX2": + case "SIP": + case "ZAP": + $sql .= " WHERE tech = '".strtolower($tech)."'"; + break; + case "ALL": + default: + } + $sql .= ' ORDER BY id'; + $results = sql($sql,"getAll",DB_FETCHMODE_ASSOC); + + $extens = null; + foreach($results as $result){ + if ($get_all || checkRange($result['id'])){ + + $record = array(); + $record[0] = $result['id']; // for backwards compatibility + $record[1] = $result['description']; // for backwards compatibility + foreach ($result as $key => $value) { + $record[$key] = $value; + } + $extens[] = $record; + /* + $extens[] = array( + 0=>$result[0], // for backwards compatibility + 1=>$result[1], + 'id'=>$result[0], // FETCHMODE_ASSOC emulation + 'description'=>$result[1], + ); + */ + } + } + return $extens; +} + + +function core_devices_add($id,$tech,$dial,$devicetype,$user,$description,$emergency_cid=null,$editmode=false){ + global $amp_conf; + global $currentFile; + global $astman; + global $db; + + $display = isset($_REQUEST['display'])?$_REQUEST['display']:''; + + if (trim($id) == '' ) { + if ($display != 'extensions') { + echo ""; + } + return false; + } + + //ensure this id is not already in use + $devices = core_devices_list(); + if (is_array($devices)) { + foreach($devices as $device) { + if ($device[0] === $id) { + if ($display <> 'extensions') echo ""; + return false; + } + } + } + //unless defined, $dial is TECH/id + if ( $dial == '' ) { + //zap is an exception + if ( strtolower($tech) == "zap" ) { + $zapchan = $_REQUEST['devinfo_channel'] != '' ? $_REQUEST['devinfo_channel'] : $_REQUEST['channel']; + $dial = 'ZAP/'.$zapchan; + } else { + $dial = strtoupper($tech)."/".$id; + } + } + + //check to see if we are requesting a new user + if ($user == "new") { + $user = $id; + $jump = true; + } + + if(!get_magic_quotes_gpc()) { + if(!empty($emergency_cid)) + $emergency_cid = $db->escapeSimple($emergency_cid); + if(!empty($description)) + $description = $db->escapeSimple($description); + } + + //insert into devices table + $sql="INSERT INTO devices (id,tech,dial,devicetype,user,description,emergency_cid) values (\"$id\",\"$tech\",\"$dial\",\"$devicetype\",\"$user\",\"$description\",\"$emergency_cid\")"; + sql($sql); + + //add details to astdb + if ($astman) { + // if adding or editting a fixed device, user property should always be set + if ($devicetype == 'fixed' || !$editmode) { + $astman->database_put("DEVICE",$id."/user",$user); + } + // If changing from a fixed to an adhoc, the user property should be intialized + // to the new default, not remain as the previous fixed user + if ($editmode) { + $previous_type = $astman->database_get("DEVICE",$id."/type"); + if ($previous_type == 'fixed' && $devicetype == 'adhoc') { + $astman->database_put("DEVICE",$id."/user",$user); + } + } + $astman->database_put("DEVICE",$id."/dial",$dial); + $astman->database_put("DEVICE",$id."/type",$devicetype); + $astman->database_put("DEVICE",$id."/default_user",$user); + if($emergency_cid != '') { + $astman->database_put("DEVICE",$id."/emergency_cid","\"".$emergency_cid."\""); + } + + if ($user != "none") { + $existingdevices = $astman->database_get("AMPUSER",$user."/device"); + if (empty($existingdevices)) { + $astman->database_put("AMPUSER",$user."/device",$id); + } else { + $existingdevices_array = explode('&',$existingdevices); + if (!in_array($id, $existingdevices_array)) { + $existingdevices_array[]=$id; + $existingdevices = implode('&',$existingdevices_array); + $astman->database_put("AMPUSER",$user."/device",$existingdevices); + } + } + } + + } else { + fatal("Cannot connect to Asterisk Manager with ".$amp_conf["AMPMGRUSER"]."/".$amp_conf["AMPMGRPASS"]); + } + + // create a voicemail symlink if needed + $thisUser = core_users_get($user); + if(isset($thisUser['voicemail']) && ($thisUser['voicemail'] != "novm")) { + if(empty($thisUser['voicemail'])) { + $vmcontext = "default"; + } else { + $vmcontext = $thisUser['voicemail']; + } + + //voicemail symlink + exec("rm -f /var/spool/asterisk/voicemail/device/".$id); + exec("/bin/ln -s /var/spool/asterisk/voicemail/".$vmcontext."/".$user."/ /var/spool/asterisk/voicemail/device/".$id); + } + + //take care of sip/iax/zap config + $funct = "core_devices_add".strtolower($tech); + if(function_exists($funct)){ + $funct($id); + } + +/* if($user != "none") { + core_hint_add($user); + }*/ + + //if we are requesting a new user, let's jump to users.php + if (isset($jump)) { + echo(""); + } + return true; +} + +function core_devices_del($account,$editmode=false){ + global $amp_conf; + global $currentFile; + global $astman; + + //get all info about device + $devinfo = core_devices_get($account); + + //delete details to astdb + if ($astman) { + // If a user was selected, remove this device from the user + $deviceuser = $astman->database_get("DEVICE",$account."/user"); + if (isset($deviceuser) && $deviceuser != "none") { + // Remove the device record from the user's device list + $userdevices = $astman->database_get("AMPUSER",$deviceuser."/device"); + + // We need to remove just this user and leave the rest alone + $userdevicesarr = explode("&", $userdevices); + $userdevicesarr_hash = array_flip($userdevicesarr); + unset($userdevicesarr_hash[$account]); + $userdevicesarr = array_flip($userdevicesarr_hash); + $userdevices = implode("&", $userdevicesarr); + + if (empty($userdevices)) { + $astman->database_del("AMPUSER",$deviceuser."/device"); + } else { + $astman->database_put("AMPUSER",$deviceuser."/device",$userdevices); + } + } + if (! $editmode) { + $astman->database_del("DEVICE",$account."/dial"); + $astman->database_del("DEVICE",$account."/type"); + $astman->database_del("DEVICE",$account."/user"); + $astman->database_del("DEVICE",$account."/default_user"); + $astman->database_del("DEVICE",$account."/emergency_cid"); + } + + //delete from devices table + $sql="DELETE FROM devices WHERE id = \"$account\""; + sql($sql); + + //voicemail symlink + exec("rm -f /var/spool/asterisk/voicemail/device/".$account); + } else { + fatal("Cannot connect to Asterisk Manager with ".$amp_conf["AMPMGRUSER"]."/".$amp_conf["AMPMGRPASS"]); + } + + //take care of sip/iax/zap config + $funct = "core_devices_del".strtolower($devinfo['tech']); + if(function_exists($funct)){ + $funct($account); + } +} + +function core_devices_get($account){ + //get all the variables for the meetme + $sql = "SELECT * FROM devices WHERE id = '$account'"; + $results = sql($sql,"getRow",DB_FETCHMODE_ASSOC); + + //take care of sip/iax/zap config + $funct = "core_devices_get".strtolower($results['tech']); + if (!empty($results['tech']) && function_exists($funct)) { + $devtech = $funct($account); + if (is_array($devtech)){ + $results = array_merge($results,$devtech); + } + } + + return $results; +} + +// this function rebuilds the astdb based on device table contents +// used on devices.php if action=resetall +function core_devices2astdb(){ + global $astman; + global $amp_conf; + + $sql = "SELECT * FROM devices"; + $devresults = sql($sql,"getAll",DB_FETCHMODE_ASSOC); + + //add details to astdb + if ($astman) { + $astman->database_deltree("DEVICE"); + foreach ($devresults as $dev) { + extract($dev); + $astman->database_put("DEVICE",$id."/dial",$dial); + $astman->database_put("DEVICE",$id."/type",$devicetype); + $astman->database_put("DEVICE",$id."/user",$user); + $astman->database_put("DEVICE",$id."/default_user",$user); + if(trim($emergency_cid) != '') { + $astman->database_put("DEVICE",$id."/emergency_cid","\"".$emergency_cid."\""); + } + // If a user is selected, add this device to the user + if ($user != "none") { + $existingdevices = $astman->database_get("AMPUSER",$user."/device"); + if (!empty($existingdevices)) { + $existingdevices .= "&"; + } + $astman->database_put("AMPUSER",$user."/device",$existingdevices.$id); + } + + // create a voicemail symlink if needed + $thisUser = core_users_get($user); + if(isset($thisUser['voicemail']) && ($thisUser['voicemail'] != "novm")) { + if(empty($thisUser['voicemail'])) + $vmcontext = "default"; + else + $vmcontext = $thisUser['voicemail']; + //voicemail symlink + exec("rm -f /var/spool/asterisk/voicemail/device/".$id); + exec("/bin/ln -s /var/spool/asterisk/voicemail/".$vmcontext."/".$user."/ /var/spool/asterisk/voicemail/device/".$id); + } + } + return true; + } else { + return false; + } +} + +// this function rebuilds the astdb based on users table contents +// used on devices.php if action=resetall +function core_users2astdb(){ + global $amp_conf; + global $astman; + global $db; + + $sql = "SELECT * FROM users"; + $userresults = sql($sql,"getAll",DB_FETCHMODE_ASSOC); + + //add details to astdb + if ($astman) { + foreach($userresults as $usr) { + extract($usr); + $astman->database_put("AMPUSER",$extension."/password",$password); + $astman->database_put("AMPUSER",$extension."/ringtimer",$ringtimer); + $astman->database_put("AMPUSER",$extension."/noanswer",$noanswer); + $astman->database_put("AMPUSER",$extension."/recording",$recording); + $astman->database_put("AMPUSER",$extension."/outboundcid","\"".$db->escapeSimple($outboundcid)."\""); + $astman->database_put("AMPUSER",$extension."/cidname","\"".$db->escapeSimple($name)."\""); + $astman->database_put("AMPUSER",$extension."/voicemail","\"".$voicemail."\""); + } + return true; + } else { + return false; + } + +// TODO: this was... +// return $astman->disconnect(); +// is "true" the correct value...? +} + +//add to sip table +function core_devices_addsip($account) { + global $db; + global $currentFile; + + foreach ($_REQUEST as $req=>$data) { + if ( substr($req, 0, 8) == 'devinfo_' ) { + $keyword = substr($req, 8); + if ( $keyword == 'dial' && $data == '' ) { + $sipfields[] = array($account, $keyword, 'SIP/'.$account); + } elseif ($keyword == 'mailbox' && $data == '') { + $sipfields[] = array($account,'mailbox',$account.'@device'); + } else { + $sipfields[] = array($account, $keyword, $data); + } + } + } + + if ( !is_array($sipfields) ) { // left for compatibilty....lord knows why ! + $sipfields = array( + //array($account,'account',$account), + array($account,'accountcode',(isset($_REQUEST['accountcode']))?$_REQUEST['accountcode']:''), + array($account,'secret',(isset($_REQUEST['secret']))?$_REQUEST['secret']:''), + array($account,'canreinvite',(isset($_REQUEST['canreinvite']))?$_REQUEST['canreinvite']:'no'), + array($account,'context',(isset($_REQUEST['context']))?$_REQUEST['context']:'from-internal'), + array($account,'dtmfmode',(isset($_REQUEST['dtmfmode']))?$_REQUEST['dtmfmode']:''), + array($account,'host',(isset($_REQUEST['host']))?$_REQUEST['host']:'dynamic'), + array($account,'type',(isset($_REQUEST['type']))?$_REQUEST['type']:'friend'), + array($account,'mailbox',(isset($_REQUEST['mailbox']) && !empty($_REQUEST['mailbox']))?$_REQUEST['mailbox']:$account.'@device'), + array($account,'username',(isset($_REQUEST['username']))?$_REQUEST['username']:$account), + array($account,'nat',(isset($_REQUEST['nat']))?$_REQUEST['nat']:'yes'), + array($account,'port',(isset($_REQUEST['port']))?$_REQUEST['port']:'5060'), + array($account,'qualify',(isset($_REQUEST['qualify']))?$_REQUEST['qualify']:'yes'), + array($account,'callgroup',(isset($_REQUEST['callgroup']))?$_REQUEST['callgroup']:''), + array($account,'pickupgroup',(isset($_REQUEST['pickupgroup']))?$_REQUEST['pickupgroup']:''), + array($account,'disallow',(isset($_REQUEST['disallow']))?$_REQUEST['disallow']:''), + array($account,'allow',(isset($_REQUEST['allow']))?$_REQUEST['allow']:'') + //array($account,'record_in',(isset($_REQUEST['record_in']))?$_REQUEST['record_in']:'On-Demand'), + //array($account,'record_out',(isset($_REQUEST['record_out']))?$_REQUEST['record_out']:'On-Demand'), + //array($account,'callerid',(isset($_REQUEST['description']))?$_REQUEST['description']." <".$account.'>':'device'." <".$account.'>') + ); + } + + // Very bad + $sipfields[] = array($account,'account',$account); + $sipfields[] = array($account,'callerid',(isset($_REQUEST['description']) && $_REQUEST['description'])?$_REQUEST['description']." <".$account.'>':'device'." <".$account.'>'); + + // Where is this in the interface ?????? + $sipfields[] = array($account,'record_in',($_REQUEST['record_in'])?$_REQUEST['record_in']:'On-Demand'); + $sipfields[] = array($account,'record_out',($_REQUEST['record_out'])?$_REQUEST['record_out']:'On-Demand'); + + $compiled = $db->prepare('INSERT INTO sip (id, keyword, data) values (?,?,?)'); + $result = $db->executeMultiple($compiled,$sipfields); + if(DB::IsError($result)) { + die_freepbx($result->getDebugInfo()."

".'error adding to SIP table'); + } +} + +function core_devices_delsip($account) { + global $db; + global $currentFile; + + $sql = "DELETE FROM sip WHERE id = '$account'"; + $result = $db->query($sql); + + if(DB::IsError($result)) { + die_freepbx($result->getMessage().$sql); + } +} + +function core_devices_getsip($account) { + global $db; + $sql = "SELECT keyword,data FROM sip WHERE id = '$account'"; + $results = $db->getAssoc($sql); + if(DB::IsError($results)) { + $results = null; + } + + return $results; +} + +//add to iax table +function core_devices_addiax2($account) { + global $db; + global $currentFile; + + foreach ($_REQUEST as $req=>$data) { + if ( substr($req, 0, 8) == 'devinfo_' ) { + $keyword = substr($req, 8); + if ( $keyword == 'dial' && $data == '' ) { + $iaxfields[] = array($account, $keyword, 'IAX2/'.$account); + } elseif ($keyword == 'mailbox' && $data == '') { + $iaxfields[] = array($account,'mailbox',$account.'@device'); + } else { + $iaxfields[] = array($account, $keyword, $data); + } + } + } + + if ( !is_array($iaxfields) ) { // left for compatibilty....lord knows why ! + $iaxfields = array( + //array($account,'account',$account), + array($account,'secret',($_REQUEST['secret'])?$_REQUEST['secret']:''), + array($account,'notransfer',($_REQUEST['notransfer'])?$_REQUEST['notransfer']:'yes'), + array($account,'context',($_REQUEST['context'])?$_REQUEST['context']:'from-internal'), + array($account,'host',($_REQUEST['host'])?$_REQUEST['host']:'dynamic'), + array($account,'type',($_REQUEST['type'])?$_REQUEST['type']:'friend'), + array($account,'mailbox',($_REQUEST['mailbox'])?$_REQUEST['mailbox']:$account.'@device'), + array($account,'username',($_REQUEST['username'])?$_REQUEST['username']:$account), + array($account,'port',($_REQUEST['port'])?$_REQUEST['port']:'4569'), + array($account,'qualify',($_REQUEST['qualify'])?$_REQUEST['qualify']:'yes'), + array($account,'disallow',($_REQUEST['disallow'])?$_REQUEST['disallow']:''), + array($account,'allow',($_REQUEST['allow'])?$_REQUEST['allow']:''), + array($account,'accountcode',($_REQUEST['accountcode'])?$_REQUEST['accountcode']:'') + //array($account,'record_in',($_REQUEST['record_in'])?$_REQUEST['record_in']:'On-Demand'), + //array($account,'record_out',($_REQUEST['record_out'])?$_REQUEST['record_out']:'On-Demand'), + //array($account,'callerid',($_REQUEST['description'])?$_REQUEST['description']." <".$account.'>':'device'." <".$account.'>') + ); + } + + // Very bad + $iaxfields[] = array($account,'account',$account); + $iaxfields[] = array($account,'callerid',(isset($_REQUEST['description']) && $_REQUEST['description'] != '')?$_REQUEST['description']." <".$account.'>':'device'." <".$account.'>'); + // Asterisk treats no caller ID from an IAX device as 'hide callerid', and ignores the caller ID + // set in iax.conf. As we rely on this for pretty much everything, we need to specify the + // callerid as a variable which gets picked up in macro-callerid. + // Ref - http://bugs.digium.com/view.php?id=456 + $iaxfields[] = array($account,'setvar',"REALCALLERIDNUM=$account"); + + // Where is this in the interface ?????? + $iaxfields[] = array($account,'record_in',($_REQUEST['record_in'])?$_REQUEST['record_in']:'On-Demand'); + $iaxfields[] = array($account,'record_out',($_REQUEST['record_out'])?$_REQUEST['record_out']:'On-Demand'); + + $compiled = $db->prepare('INSERT INTO iax (id, keyword, data) values (?,?,?)'); + $result = $db->executeMultiple($compiled,$iaxfields); + if(DB::IsError($result)) { + die_freepbx($result->getMessage()."

error adding to IAX table"); + } +} + +function core_devices_deliax2($account) { + global $db; + global $currentFile; + + $sql = "DELETE FROM iax WHERE id = '$account'"; + $result = $db->query($sql); + + if(DB::IsError($result)) { + die_freepbx($result->getMessage().$sql); + } +} + +function core_devices_getiax2($account) { + global $db; + $sql = "SELECT keyword,data FROM iax WHERE id = '$account'"; + $results = $db->getAssoc($sql); + if(DB::IsError($results)) { + $results = null; + } + + return $results; +} + +function core_devices_addzap($account) { + global $db; + global $currentFile; + + foreach ($_REQUEST as $req=>$data) { + if ( substr($req, 0, 8) == 'devinfo_' ) { + $keyword = substr($req, 8); + if ( $keyword == 'dial' && $data == '' ) { + $zapchan = $_REQUEST['devinfo_channel'] != '' ? $_REQUEST['devinfo_channel'] : $_REQUEST['channel']; + $zapfields[] = array($account, $keyword, 'ZAP/'.$zapchan); + } elseif ($keyword == 'mailbox' && $data == '') { + $zapfields[] = array($account,'mailbox',$account.'@device'); + } else { + $zapfields[] = array($account, $keyword, $data); + } + } + } + + if ( !is_array($zapfields) ) { // left for compatibilty....lord knows why ! + $zapfields = array( + //array($account,'account',$account), + array($account,'context',($_REQUEST['context'])?$_REQUEST['context']:'from-internal'), + array($account,'mailbox',($_REQUEST['mailbox'])?$_REQUEST['mailbox']:$account.'@device'), + //array($account,'callerid',($_REQUEST['description'])?$_REQUEST['description']." <".$account.'>':'device'." <".$account.'>'), + array($account,'immediate',($_REQUEST['immediate'])?$_REQUEST['immediate']:'no'), + array($account,'signalling',($_REQUEST['signalling'])?$_REQUEST['signalling']:'fxo_ks'), + array($account,'echocancel',($_REQUEST['echocancel'])?$_REQUEST['echocancel']:'yes'), + array($account,'echocancelwhenbridged',($_REQUEST['echocancelwhenbridged'])?$_REQUEST['echocancelwhenbridged']:'no'), + array($account,'immediate',($_REQUEST['immediate'])?$_REQUEST['immediate']:'no'), + array($account,'echotraining',($_REQUEST['echotraining'])?$_REQUEST['echotraining']:'800'), + array($account,'busydetect',($_REQUEST['busydetect'])?$_REQUEST['busydetect']:'no'), + array($account,'busycount',($_REQUEST['busycount'])?$_REQUEST['busycount']:'7'), + array($account,'callprogress',($_REQUEST['callprogress'])?$_REQUEST['callprogress']:'no'), + //array($account,'record_in',($_REQUEST['record_in'])?$_REQUEST['record_in']:'On-Demand'), + //array($account,'record_out',($_REQUEST['record_out'])?$_REQUEST['record_out']:'On-Demand'), + array($account,'accountcode',(isset($_REQUEST['accountcode']))?$_REQUEST['accountcode']:''), + array($account,'callgroup',(isset($_REQUEST['callgroup']))?$_REQUEST['callgroup']:''), + array($account,'pickupgroup',(isset($_REQUEST['pickupgroup']))?$_REQUEST['pickupgroup']:''), + array($account,'channel',($_REQUEST['channel'])?$_REQUEST['channel']:'') + ); + } + + // Very bad + $zapfields[] = array($account,'account',$account); + $zapfields[] = array($account,'callerid',($_REQUEST['description'])?$_REQUEST['description']." <".$account.'>':'device'." <".$account.'>'); + + // Where is this in the interface ?????? + $zapfields[] = array($account,'record_in',($_REQUEST['record_in'])?$_REQUEST['record_in']:'On-Demand'); + $zapfields[] = array($account,'record_out',($_REQUEST['record_out'])?$_REQUEST['record_out']:'On-Demand'); + + $compiled = $db->prepare('INSERT INTO zap (id, keyword, data) values (?,?,?)'); + $result = $db->executeMultiple($compiled,$zapfields); + if(DB::IsError($result)) { + die_freepbx($result->getMessage()."

error adding to ZAP table"); + } +} + +function core_devices_delzap($account) { + global $db; + global $currentFile; + + $sql = "DELETE FROM zap WHERE id = '$account'"; + $result = $db->query($sql); + if(DB::IsError($result)) { + die_freepbx($result->getMessage().$sql); + } +} + +function core_devices_getzap($account) { + global $db; + $sql = "SELECT keyword,data FROM zap WHERE id = '$account'"; + $results = $db->getAssoc($sql); + if(DB::IsError($results)) { + $results = null; + } + return $results; +} +/* end page.devices.php functions */ + + + + +function core_hint_get($account){ + global $astman; + + $chan_dahdi = ast_with_dahdi(); + // We should always check the AMPUSER in case they logged into a device + // but we will fall back to the old methond if $astman not open although + // I'm pretty sure everything else will puke anyhow if not running + // + if ($astman) { + $device=$astman->database_get("AMPUSER",$account."/device"); + $device_arr = explode('&',$device); + $sql = "SELECT dial from devices where id in ('".implode("','",$device_arr)."')"; + } else { + $sql = "SELECT dial from devices where user = '{$account}'"; + } + $results = sql($sql,"getAll",DB_FETCHMODE_ASSOC); + + //create an array of strings + if (is_array($results)){ + foreach ($results as $result) { + if ($chan_dahdi) { + $dial[] = str_replace('ZAP', 'DAHDI', $result['dial']); + } else { + $dial[] = $result['dial']; + } + } + } + + //create a string with & delimiter + if (isset($dial) && is_array($dial)){ + $hint = implode($dial,"&"); + } else { + if (isset($results[0]['dial'])) { + $hint = $results[0]['dial']; + } else { + $hint = null; + } + } + + return $hint; +} + + + +/* begin page.users.php functions */ + +// get the existing extensions +// the returned arrays contain [0]:extension [1]:name +function core_users_list() { + $results = sql("SELECT extension,name,voicemail FROM users ORDER BY extension","getAll"); + + //only allow extensions that are within administrator's allowed range + foreach($results as $result){ + if (checkRange($result[0])){ + $extens[] = array($result[0],$result[1],$result[2]); + } + } + + if (isset($extens)) { + sort($extens); + return $extens; + } else { + return null; + } +} + +function core_check_extensions($exten=true) { + global $amp_conf; + + $extenlist = array(); + if (is_array($exten) && empty($exten)) { + return $extenlist; + } + $sql = "SELECT extension, name FROM users "; + if (is_array($exten)) { + $sql .= "WHERE extension in ('".implode("','",$exten)."')"; + } + $sql .= " ORDER BY extension"; + $results = sql($sql,"getAll",DB_FETCHMODE_ASSOC); + + foreach ($results as $result) { + $thisexten = $result['extension']; + $extenlist[$thisexten]['description'] = _("User Extension: ").$result['name']; + $extenlist[$thisexten]['status'] = 'INUSE'; + $display = ($amp_conf['AMPEXTENSIONS'] == "deviceanduser")?'users':'extensions'; + $extenlist[$thisexten]['edit_url'] = "config.php?type=setup&display=$display&extdisplay=".urlencode($thisexten)."&skip=0"; + } + return $extenlist; +} + +function core_check_destinations($dest=true) { + global $active_modules; + + $destlist = array(); + if (is_array($dest) && empty($dest)) { + return $destlist; + } + $sql = "SELECT extension, cidnum, description, destination FROM incoming "; + if ($dest !== true) { + $sql .= "WHERE destination in ('".implode("','",$dest)."')"; + } + $sql .= "ORDER BY extension, cidnum"; + $results = sql($sql,"getAll",DB_FETCHMODE_ASSOC); + + //$type = isset($active_modules['announcement']['type'])?$active_modules['announcement']['type']:'setup'; + + foreach ($results as $result) { + $thisdest = $result['destination']; + $thisid = $result['extension'].'/'.$result['cidnum']; + $destlist[] = array( + 'dest' => $thisdest, + 'description' => 'Inbound Route: '.$result['description'].' ('.$thisid.')', + 'edit_url' => 'config.php?display=did&extdisplay='.urlencode($thisid), + ); + } + return $destlist; +} + +function core_sipname_check($sipname, $extension) { + global $db; + if (!isset($sipname) || trim($sipname)=='') + return true; + + $sql = "SELECT sipname FROM users WHERE sipname = '$sipname' AND extension != '$extension'"; + $results = $db->getRow($sql,DB_FETCHMODE_ASSOC); + if(DB::IsError($results)) { + die_freepbx($results->getMessage().$sql); + } + + if (isset($results['sipname']) && trim($results['sipname']) == $sipname) + return false; + else + return true; +} + +function core_users_add($vars, $editmode=false) { + extract($vars); + + global $db; + global $amp_conf; + global $astman; + + $thisexten = isset($thisexten) ? $thisexten : ''; + + if (trim($extension) == '' ) { + echo ""; + return false; + } + + //ensure this id is not already in use + $extens = core_users_list(); + if(is_array($extens)) { + foreach($extens as $exten) { + if ($exten[0]===$extension) { + echo ""; + return false; + } + } + } + + $newdid_name = isset($newdid_name) ? $db->escapeSimple($newdid_name) : ''; + $newdid = isset($newdid) ? $newdid : ''; + $newdid = preg_replace("/[^0-9._XxNnZz\[\]\-\+]/" ,"", trim($newdid)); + + $newdidcid = isset($newdidcid) ? trim($newdidcid) : ''; + if (!preg_match('/^priv|^block|^unknown|^restrict|^unavail|^anonym/',strtolower($newdidcid))) { + $newdidcid = preg_replace("/[^0-9._XxNnZz\[\]\-\+]/" ,"", $newdidcid); + } + + // Well more ugliness since the javascripts are already in here + if ($newdid != '' || $newdidcid != '') { + $existing = core_did_get($newdid, $newdidcid); + if (! empty($existing)) { + echo ""."
".print_r($existing,true)."
"; + return false; + } + } + + $sipname = preg_replace("/\s/" ,"", trim($sipname)); + if (! core_sipname_check($sipname, $extension)) { + echo ""; + return false; + } + + //build the recording variable + $recording = "out=".$record_out."|in=".$record_in; + + //escape quotes and any other bad chars: + if(!get_magic_quotes_gpc()) { + $outboundcid = $db->escapeSimple($outboundcid); + $name = $db->escapeSimple($name); + } + + //if voicemail is enabled, set the box@context to use + //havn't checked but why is voicemail needed on users anyway? Doesn't exactly make it modular ! + if ( function_exists('voicemail_mailbox_get') ) { + $vmbox = voicemail_mailbox_get($extension); + if ( $vmbox == null ) { + $voicemail = "novm"; + } else { + $voicemail = $vmbox['vmcontext']; + } + } + + // Clean replace any <> with () in display name - should have javascript stopping this but ... + // + $name = preg_replace(array('//'), array('(',')'), trim($name)); + + //insert into users table + $sql="INSERT INTO users (extension,password,name,voicemail,ringtimer,noanswer,recording,outboundcid,sipname) values (\""; + $sql.= "$extension\", \""; + $sql.= isset($password)?$password:''; + $sql.= "\", \""; + $sql.= isset($name)?$name:''; + $sql.= "\", \""; + $sql.= isset($voicemail)?$voicemail:'default'; + $sql.= "\", \""; + $sql.= isset($ringtimer)?$ringtimer:''; + $sql.= "\", \""; + $sql.= isset($noanswer)?$noanswer:''; + $sql.= "\", \""; + $sql.= isset($recording)?$recording:''; + $sql.= "\", \""; + $sql.= isset($outboundcid)?$outboundcid:''; + $sql.= "\", \""; + $sql.= isset($sipname)?$sipname:''; + $sql.= "\")"; + sql($sql); + + //write to astdb + if ($astman) { + $cid_masquerade = (isset($cid_masquerade) && trim($cid_masquerade) != "")?trim($cid_masquerade):$extension; + $astman->database_put("AMPUSER",$extension."/password",isset($password)?$password:''); + $astman->database_put("AMPUSER",$extension."/ringtimer",isset($ringtimer)?$ringtimer:''); + $astman->database_put("AMPUSER",$extension."/noanswer",isset($noanswer)?$noanswer:''); + $astman->database_put("AMPUSER",$extension."/recording",isset($recording)?$recording:''); + $astman->database_put("AMPUSER",$extension."/outboundcid",isset($outboundcid)?"\"".$outboundcid."\"":''); + $astman->database_put("AMPUSER",$extension."/cidname",isset($name)?"\"".$name."\"":''); + $astman->database_put("AMPUSER",$extension."/cidnum",$cid_masquerade); + $astman->database_put("AMPUSER",$extension."/voicemail","\"".isset($voicemail)?$voicemail:''."\""); + switch ($call_screen) { + case '0': + $astman->database_del("AMPUSER",$extension."/screen"); + break; + case 'nomemory': + $astman->database_put("AMPUSER",$extension."/screen","\"nomemory\""); + break; + case 'memory': + $astman->database_put("AMPUSER",$extension."/screen","\"memory\""); + break; + default: + } + + if (!$editmode) { + $astman->database_put("AMPUSER",$extension."/device","\"".((isset($device))?$device:'')."\""); + } + + if (trim($callwaiting) == 'enabled') { + $astman->database_put("CW",$extension,"\"ENABLED\""); + } else if (trim($callwaiting) == 'disabled') { + $astman->database_del("CW",$extension); + } else { + echo "ERROR: this state should not exist
"; + } + + // Moved VmX setup to voicemail module since it is part of voicemail + // + } else { + fatal("Cannot connect to Asterisk Manager with ".$amp_conf["AMPMGRUSER"]."/".$amp_conf["AMPMGRPASS"]); + } + + // OK - got this far, if they entered a new inbound DID/CID let's deal with it now + // remember - in the nice and ugly world of this old code, $vars has been extracted + // newdid and newdidcid + + // Now if $newdid is set we need to add the DID to the routes + // + if ($newdid != '' || $newdidcid != '') { + $did_dest = 'from-did-direct,'.$extension.',1'; + $did_vars['extension'] = $newdid; + $did_vars['cidnum'] = $newdidcid; + $did_vars['faxexten'] = ''; + $did_vars['faxemail'] = ''; + $did_vars['answer'] = '0'; + $did_vars['wait'] = '0'; + $did_vars['privacyman'] = ''; + $did_vars['alertinfo'] = ''; + $did_vars['ringing'] = ''; + $did_vars['mohclass'] = 'default'; + $did_vars['description'] = $newdid_name; + $did_vars['grppre'] = ''; + $did_vars['delay_answer']= '0'; + $did_vars['pricid']= ''; + core_did_add($did_vars, $did_dest); + } + + return true; +} + +function core_users_get($extension){ + global $db; + global $amp_conf; + global $astman; + //get all the variables for the meetme + $sql = "SELECT * FROM users WHERE extension = '$extension'"; + $results = $db->getRow($sql,DB_FETCHMODE_ASSOC); + if(DB::IsError($results)) { + die_freepbx($results->getMessage().$sql); + } + if (empty($results)) { + return array(); + } + + //explode recording vars + $recording = explode("|",$results['recording']); + if (isset($recording[1])) { + $recout = substr($recording[0],4); + $recin = substr($recording[1],3); + $results['record_in']=$recin; + $results['record_out']=$recout; + } else { + $results['record_in']='Adhoc'; + $results['record_out']='Adhoc'; + } + if ($astman) { + $cw = $astman->database_get("CW",$extension); + $results['callwaiting'] = (trim($cw) == 'ENABLED') ? 'enabled' : 'disabled'; + $cid_masquerade=$astman->database_get("AMPUSER",$extension."/cidnum"); + $results['cid_masquerade'] = (trim($cid_masquerade) != "")?$cid_masquerade:$extension; + + $call_screen=$astman->database_get("AMPUSER",$extension."/screen"); + $results['call_screen'] = (trim($call_screen) != "")?$call_screen:'0'; + } else { + fatal("Cannot connect to Asterisk Manager with ".$amp_conf["AMPMGRUSER"]."/".$amp_conf["AMPMGRPASS"]); + } + + return $results; +} + +function core_users_del($extension, $editmode=false){ + global $db; + global $amp_conf; + global $astman; + + //delete from devices table + $sql="DELETE FROM users WHERE extension = \"$extension\""; + $results = $db->query($sql); + if(DB::IsError($results)) { + die_freepbx($results->getMessage().$sql); + } + + //delete details to astdb + if($astman) { + $astman->database_del("AMPUSER",$extension."/screen"); + } + if ($astman && !$editmode) { + // TODO just change this to delete everything + $astman->database_deltree("AMPUSER/".$extension); + } +} + +function core_users_directdid_get($directdid=""){ + return array(); +} + +function core_users_cleanastdb($extension) { + // This is called to remove any ASTDB traces of the user after a deletion. Otherwise, + // call forwarding, call waiting settings could hang around and bite someone if they + // recycle an extension. Is called from page.xtns and page.users. + global $amp_conf; + global $astman; + + if ($astman) { + $astman->database_del("CW",$extension); + $astman->database_del("CF",$extension); + $astman->database_del("CFB",$extension); + $astman->database_del("CFU",$extension); + + } else { + fatal("Cannot connect to Asterisk Manager with ".$amp_conf["AMPMGRUSER"]."/".$amp_conf["AMPMGRPASS"]); + } +} + +function core_users_edit($extension,$vars){ + global $db; + global $amp_conf; + global $astman; + + //I we are editing, we need to remember existing user<->device mapping, so we can delete and re-add + if ($astman) { + $ud = $astman->database_get("AMPUSER",$extension."/device"); + $current_vmcontext = $astman->database_get("AMPUSER",$extension."/voicemail"); + $new_vmcontext = isset($vars['vmcontext']) ? $vars['vmcontext'] : 'novm'; + $vars['device'] = $ud; + } else { + fatal("Cannot connect to Asterisk Manager with ".$amp_conf["AMPMGRUSER"]."/".$amp_conf["AMPMGRPASS"]); + } + + // clean and check the did to make sure it is not being used by another extension or in did routing + // + $newdid_name = isset($newdid_name) ? $db->escapeSimple($newdid_name) : ''; + $newdid = isset($vars['newdid']) ? $vars['newdid'] : ''; + $newdid = preg_replace("/[^0-9._XxNnZz\[\]\-\+]/" ,"", trim($newdid)); + + $newdidcid = isset($vars['newdidcid']) ? trim($vars['newdidcid']) : ''; + if (!preg_match('/^priv|^block|^unknown|^restrict|^unavail|^anonym/',strtolower($newdidcid))) { + $newdidcid = preg_replace("/[^0-9._XxNnZz\[\]\-\+]/" ,"", $newdidcid); + } + + // Well more ugliness since the javascripts are already in here + if ($newdid != '' || $newdidcid != '') { + $existing = core_did_get($newdid, $newdidcid); + if (! empty($existing)) { + echo ""; + return false; + } + } + + //delete and re-add + if (core_sipname_check($vars['sipname'],$extension)) { + core_users_del($extension, true); + core_users_add($vars, true); + + // If the vmcontext has changed, we need to change all the links. In extension mode, the link + // to the current fixed device will get changed, but none others will + // + if ($current_vmcontext != $new_vmcontext) { + $user_devices = explode('&',$ud); + foreach ($user_devices as $user_device) { + exec("rm -f /var/spool/asterisk/voicemail/device/".$user_device); + if ($new_context != 'novm') { + exec("/bin/ln -s /var/spool/asterisk/voicemail/".$new_vmcontext."/".$extension."/ /var/spool/asterisk/voicemail/device/".$user_device); + } + } + } + } + return true; +} + +function core_directdid_list(){ + return array(); +} + +function core_zapchandids_add($description, $channel, $did) { + global $db; + + + if (!ctype_digit(trim($channel)) || trim($channel) == '') { + echo ""; + return false; + } + if (trim($did) == '') { + echo ""; + return false; + } + + $description = q($description); + $channel = q($channel); + $did = q($did); + + $sql = "INSERT INTO zapchandids (channel, description, did) VALUES ($channel, $description, $did)"; + $results = $db->query($sql); + if (DB::IsError($results)) { + if ($results->getCode() == DB_ERROR_ALREADY_EXISTS) { + echo ""; + return false; + } else { + die_freepbx($results->getMessage()."

".$sql); + } + } + return true; +} + +function core_zapchandids_edit($description, $channel, $did) { + global $db; + + $description = q($description); + $channel = q($channel); + $did = q($did); + + $sql = "UPDATE zapchandids SET description = $description, did = $did WHERE channel = $channel"; + $results = $db->query($sql); + if (DB::IsError($results)) { + die_freepbx($results->getMessage()."

".$sql); + } + return true; +} + +function core_zapchandids_delete($channel) { + global $db; + + $channel = q($channel); + + $sql = "DELETE FROM zapchandids WHERE channel = $channel"; + $results = $db->query($sql); + if (DB::IsError($results)) { + die_freepbx($results->getMessage()."

".$sql); + } + return true; +} + +function core_zapchandids_list() { + global $db; + + $sql = "SELECT * FROM zapchandids ORDER BY channel"; + return sql($sql,"getAll",DB_FETCHMODE_ASSOC); +} + +function core_zapchandids_get($channel) { + global $db; + + $channel = q($channel); + + $sql = "SELECT * FROM zapchandids WHERE channel = $channel"; + return sql($sql,"getRow",DB_FETCHMODE_ASSOC); +} + +/* end page.users.php functions */ + + + + + +/* begin page.trunks.php functions */ + +// we're adding ,don't require a $trunknum +function core_trunks_add($tech, $channelid, $dialoutprefix, $maxchans, $outcid, $peerdetails, $usercontext, $userconfig, $register, $keepcid, $failtrunk, $disabletrunk) { + global $db; + + // find the next available ID + $trunknum = 1; + + // This is pretty ugle, will fix when we redo trunks and routes with proper uniqueids. + // get the list, sort them, then look for a hole and use it, or overflow to the end if + // not and use that + // + $trunk_hash = array(); + foreach(core_trunks_list() as $trunk) { + $trunknum = ltrim($trunk[0],"OUT_"); + $trunk_hash[] = $trunknum; + } + sort($trunk_hash); + $trunknum = 1; + foreach ($trunk_hash as $trunk_id) { + if ($trunk_id != $trunknum) { + break; + } + $trunknum++; + } + + core_trunks_backendAdd($trunknum, $tech, $channelid, $dialoutprefix, $maxchans, $outcid, $peerdetails, $usercontext, $userconfig, $register, $keepcid, $failtrunk, $disabletrunk); + + return $trunknum; +} + +function core_trunks_del($trunknum, $tech = null) { + global $db; + + if ($tech === null) { // in EditTrunk, we get this info anyways + $tech = core_trunks_getTrunkTech($trunknum); + } + + //delete from globals table + sql("DELETE FROM globals WHERE variable IN ('OUT_$trunknum','OUTCID_$trunknum','OUTMAXCHANS_$trunknum','OUTPREFIX_$trunknum','OUTKEEPCID_$trunknum','OUTFAIL_$trunknum','OUTDISABLE_$trunknum')"); + + // conditionally, delete from iax or sip + switch (strtolower($tech)) { + case "iax": + case "iax2": + sql("DELETE FROM iax WHERE id = '9999$trunknum' OR id = '99999$trunknum' OR id = '9999999$trunknum'"); + break; + case "sip": + sql("DELETE FROM sip WHERE id = '9999$trunknum' OR id = '99999$trunknum' OR id = '9999999$trunknum'"); + break; + } +} + +function core_trunks_edit($trunknum, $channelid, $dialoutprefix, $maxchans, $outcid, $peerdetails, $usercontext, $userconfig, $register, $keepcid, $failtrunk, $disabletrunk) { + //echo "editTrunk($trunknum, $channelid, $dialoutprefix, $maxchans, $outcid, $peerdetails, $usercontext, $userconfig, $register)"; + $tech = core_trunks_getTrunkTech($trunknum); + core_trunks_del($trunknum, $tech); + core_trunks_backendAdd($trunknum, $tech, $channelid, $dialoutprefix, $maxchans, $outcid, $peerdetails, $usercontext, $userconfig, $register, $keepcid, $failtrunk, $disabletrunk); +} + +// just used internally by addTrunk() and editTrunk() +//obsolete +function core_trunks_backendAdd($trunknum, $tech, $channelid, $dialoutprefix, $maxchans, $outcid, $peerdetails, $usercontext, $userconfig, $register, $keepcid, $failtrunk, $disabletrunk) { + global $db; + + if (is_null($dialoutprefix)) $dialoutprefix = ""; // can't be NULL + + //echo "backendAddTrunk($trunknum, $tech, $channelid, $dialoutprefix, $maxchans, $outcid, $peerdetails, $usercontext, $userconfig, $register)"; + + // change iax to "iax2" (only spot we actually store iax2, since its used by Dial()..) + $techtemp = ((strtolower($tech) == "iax") ? "iax2" : $tech); + $outval = (($techtemp == "custom") ? "AMP:".$channelid : strtoupper($techtemp).'/'.$channelid); + + + $glofields = array( + array('OUT_'.$trunknum, $outval), + array('OUTPREFIX_'.$trunknum, $dialoutprefix), + array('OUTMAXCHANS_'.$trunknum, $maxchans), + array('OUTCID_'.$trunknum, $outcid), + array('OUTKEEPCID_'.$trunknum, $keepcid), + array('OUTFAIL_'.$trunknum, $failtrunk), + array('OUTDISABLE_'.$trunknum, $disabletrunk), + ); + + unset($techtemp); + + $compiled = $db->prepare('INSERT INTO globals (variable, value) values (?,?)'); + $result = $db->executeMultiple($compiled,$glofields); + if(DB::IsError($result)) { + die_freepbx($result->getMessage()."

".$sql); + } + + $disable_flag = ($disabletrunk == "on")?1:0; + + switch (strtolower($tech)) { + case "iax": + case "iax2": + core_trunks_addSipOrIax($peerdetails,'iax',$channelid,$trunknum,$disable_flag); + if ($usercontext != ""){ + core_trunks_addSipOrIax($userconfig,'iax',$usercontext,'9'.$trunknum,$disable_flag); + } + if ($register != ""){ + core_trunks_addRegister($trunknum,'iax',$register,$disable_flag); + } + break; + case "sip": + core_trunks_addSipOrIax($peerdetails,'sip',$channelid,$trunknum,$disable_flag); + if ($usercontext != ""){ + core_trunks_addSipOrIax($userconfig,'sip',$usercontext,'9'.$trunknum,$disable_flag); + } + if ($register != ""){ + core_trunks_addRegister($trunknum,'sip',$register,$disable_flag); + } + break; + } +} + +function core_trunks_getTrunkTech($trunknum) { + + $results = sql("SELECT value FROM globals WHERE variable = 'OUT_".$trunknum."'","getAll"); + if (!$results) { + return false; + } + if(strpos($results[0][0],"AMP:") === 0) { //custom trunks begin with AMP: + $tech = "custom"; + } else { + $tech = strtolower( strtok($results[0][0],'/') ); // the technology. ie: ZAP/g0 is ZAP + + if ($tech == "iax2") $tech = "iax"; // same thing, here + } + return $tech; +} + +//add trunk info to sip or iax table +function core_trunks_addSipOrIax($config,$table,$channelid,$trunknum,$disable_flag=0) { + global $db; + + $confitem['account'] = $channelid; + $gimmieabreak = nl2br($config); + $lines = split('
',$gimmieabreak); + foreach ($lines as $line) { + $line = trim($line); + if (count(split('=',$line)) > 1) { + $tmp = split('=',$line,2); + $key=trim($tmp[0]); + $value=trim($tmp[1]); + if (isset($confitem[$key]) && !empty($confitem[$key])) + $confitem[$key].="&".$value; + else + $confitem[$key]=$value; + } + } + // rember 1=disabled so we start at 2 (1 + the first 1) + $seq = 1; + foreach($confitem as $k=>$v) { + $seq = ($disable_flag == 1) ? 1 : $seq+1; + $dbconfitem[]=array($k,$v,$seq); + } + $compiled = $db->prepare("INSERT INTO $table (id, keyword, data, flags) values ('9999$trunknum',?,?,?)"); + $result = $db->executeMultiple($compiled,$dbconfitem); + if(DB::IsError($result)) { + die_freepbx($result->getMessage()."

INSERT INTO $table (id, keyword, data, flags) values ('9999$trunknum',?,?,'$disable_flag')"); + } +} + +//Sort trunks for sqlite +function sort_trunks($a,$b) { + global $unique_trunks; + ereg("OUT_([0-9]+)",$unique_trunks[$a][0],$trunk_num1); + ereg("OUT_([0-9]+)",$unique_trunks[$b][0],$trunk_num2); + return ($trunk_num1[1] >= $trunk_num2[1]? 1:-1); +} + +//get unique trunks +function core_trunks_list($assoc = false) { + // TODO: $assoc default to true, eventually.. + + global $db; + global $amp_conf; + + // sqlite doesn't support the syntax required for the SQL so we have to do it the hard way + if ($amp_conf["AMPDBENGINE"] == "sqlite3") + { + $sqlstr = "SELECT variable, value FROM globals WHERE variable LIKE 'OUT\_%' ESCAPE '\'"; + $my_unique_trunks = sql($sqlstr,"getAll",DB_FETCHMODE_ASSOC); + + $sqlstr = "SELECT variable, value FROM globals WHERE variable LIKE 'OUTDISABLE\_%' ESCAPE '\'"; + $disable_states = sql($sqlstr,"getAll",DB_FETCHMODE_ASSOC); + + foreach($disable_states as $arr) { + $disable_states_assoc[$arr['variable']] = $arr['value']; + } + global $unique_trunks; + $unique_trunks = array(); + + foreach ($my_unique_trunks as $this_trunk) { + + $trunk_num = substr($this_trunk['variable'],4); + $this_state = (isset($disable_states_assoc['OUTDISABLE_'.$trunk_num]) ? $disable_states_assoc['OUTDISABLE_'.$trunk_num] : 'off'); + $unique_trunks[] = array($this_trunk['variable'], $this_trunk['value'], $this_state); + } + // sort this array using a custom function sort_trunks(), defined above + uksort($unique_trunks,"sort_trunks"); + // re-index the newly sorted array + foreach($unique_trunks as $arr) { + $unique_trunks_t[] = array($arr[0],$arr[1],$arr[2]); + } + $unique_trunks = $unique_trunks_t; + + } + else + { + $sqlstr = "SELECT t.variable, t.value, d.value state FROM `globals` t "; + $sqlstr .= "JOIN (SELECT x.variable, x.value FROM globals x WHERE x.variable LIKE 'OUTDISABLE\_%') d "; + $sqlstr .= "ON substring(t.variable,5) = substring(d.variable,12) WHERE t.variable LIKE 'OUT\_%' "; + $sqlstr .= "UNION ALL "; + $sqlstr .= "SELECT v.variable, v.value, concat(substring(v.value,1,0),'off') state FROM `globals` v "; + $sqlstr .= "WHERE v.variable LIKE 'OUT\_%' AND concat('OUTDISABLE_',substring(v.variable,5)) NOT IN "; + $sqlstr .= " ( SELECT variable from globals WHERE variable LIKE 'OUTDISABLE\_%' ) "; + $sqlstr .= "ORDER BY variable"; + //$unique_trunks = sql("SELECT * FROM globals WHERE variable LIKE 'OUT_%' ORDER BY variable","getAll"); + $unique_trunks = sql($sqlstr,"getAll"); + } + + //if no trunks have ever been defined, then create the proper variables with the default zap trunk + // TODO: this looks dumb, updated to remove deprecated values but why can't there be no trunks? + // + if (count($unique_trunks) == 0) + { + sql("INSERT INTO globals (variable, value) values ('OUT_1','ZAP/g0')"); + $unique_trunks[] = array('OUT_1','ZAP/g0'); + } + // asort($unique_trunks); + + if ($assoc) { + $trunkinfo = array(); + + foreach ($unique_trunks as $trunk) { + list($tech,$name) = explode('/',$trunk[1]); + $trunkinfo[$trunk[1]] = array( + 'name' => $name, + 'tech' => $tech, + 'globalvar' => $trunk[0], // ick + 'value' => $trunk[2], // ?? no idea what this is. + ); + } + return $trunkinfo; + } else { + return $unique_trunks; + } +} + +function core_trunks_addRegister($trunknum,$tech,$reg,$disable_flag=0) { + sql("INSERT INTO $tech (id, keyword, data, flags) values ('9999999$trunknum','register','$reg','$disable_flag')"); +} + + +function core_trunks_addDialRules($trunknum, $dialrules) { + $values = array(); + $i = 1; + foreach ($dialrules as $rule) { + $values["rule".$i++] = $rule; + } + + $conf = core_trunks_readDialRulesFile(); + + // rewrite for this trunk + $conf["trunk-".$trunknum] = $values; + + core_trunks_writeDialRulesFile($conf); +} + +function core_trunks_readDialRulesFile() { + global $amp_conf; + $localPrefixFile = $amp_conf['ASTETCDIR']."/localprefixes.conf"; + + core_trunks_parse_conf($localPrefixFile, $conf, $section); + + return $conf; +} + +function core_trunks_writeDialRulesFile($conf) { + global $amp_conf; + $localPrefixFile = $amp_conf['ASTETCDIR']."/localprefixes.conf"; + + $fd = fopen($localPrefixFile,"w"); + foreach ($conf as $section=>$values) { + fwrite($fd, "[".$section."]\n"); + foreach ($values as $key=>$value) { + fwrite($fd, $key."=".$value."\n"); + } + fwrite($fd, "\n"); + } + fclose($fd); +} + +function core_trunks_parse_conf($filename, &$conf, &$section) { + if (is_null($conf)) { + $conf = array(); + } + if (is_null($section)) { + $section = "general"; + } + + if (file_exists($filename)) { + $fd = fopen($filename, "r"); + while ($line = fgets($fd, 1024)) { + if (preg_match("/^\s*([a-zA-Z0-9-_]+)\s*=\s*(.*?)\s*([;#].*)?$/",$line,$matches)) { + // name = value + // option line + $conf[$section][ $matches[1] ] = $matches[2]; + } else if (preg_match("/^\s*\[(.+)\]/",$line,$matches)) { + // section name + $section = strtolower($matches[1]); + } 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]; + } + + core_trunks_parse_conf($filename, $conf, $section); + } + } + } +} + +function core_trunks_getTrunkTrunkName($trunknum) { + $results = sql("SELECT value FROM globals WHERE variable = 'OUT_".$trunknum."'","getAll"); + if (!$results) { + return false; + } + + if(strpos($results[0][0],"AMP:") === 0) { //custom trunks begin with AMP: + $tname = substr($results[0][0],4); + } else { + strtok($results[0][0],'/'); + $tname = strtok('/'); // the text _after_ technology. ie: ZAP/g0 is g0 + } + return $tname; +} + +//get and print peer details (prefixed with 4 9's) +function core_trunks_getTrunkPeerDetails($trunknum) { + global $db; + + $tech = core_trunks_getTrunkTech($trunknum); + + if ($tech == "zap") return ""; // zap has no details + + $results = sql("SELECT keyword,data FROM $tech WHERE id = '9999$trunknum' ORDER BY flags, keyword DESC","getAll"); + + foreach ($results as $result) { + if ($result[0] != 'account') { + if (isset($confdetail)) + $confdetail .= $result[0] .'='. $result[1] . "\n"; + else + $confdetail = $result[0] .'='. $result[1] . "\n"; + } + } + return $confdetail; +} + +//get trunk user context (prefixed with 5 9's) +function core_trunks_getTrunkUserContext($trunknum) { + $tech = core_trunks_getTrunkTech($trunknum); + if ($tech == "zap") return ""; // zap has no account + + $results = sql("SELECT keyword,data FROM $tech WHERE id = '99999$trunknum'","getAll"); + + foreach ($results as $result) { + if ($result[0] == 'account') { + $account = $result[1]; + } + } + return isset($account)?$account:null; +} + +//get and print user config (prefixed with 5 9's) +function core_trunks_getTrunkUserConfig($trunknum) { + global $db; + + $tech = core_trunks_getTrunkTech($trunknum); + + if ($tech == "zap") return ""; // zap has no details + + $results = sql("SELECT keyword,data FROM $tech WHERE id = '99999$trunknum' ORDER BY flags, keyword DESC","getAll"); + + foreach ($results as $result) { + if ($result[0] != 'account') { + if (isset($confdetail)) + $confdetail .= $result[0] .'='. $result[1] . "\n"; + else + $confdetail = $result[0] .'='. $result[1] . "\n"; + } + } + return isset($confdetail)?$confdetail:null; +} + +//get trunk account register string +function core_trunks_getTrunkRegister($trunknum) { + $tech = core_trunks_getTrunkTech($trunknum); + + if ($tech == "zap") return ""; // zap has no register + + $results = sql("SELECT keyword,data FROM $tech WHERE id = '9999999$trunknum'","getAll"); + + foreach ($results as $result) { + $register = $result[1]; + } + return isset($register)?$register:null; +} + +function core_trunks_getDialRules($trunknum) { + $conf = core_trunks_readDialRulesFile(); + if (isset($conf["trunk-".$trunknum])) { + return $conf["trunk-".$trunknum]; + } + return false; +} + +//get outbound routes for a given trunk +function core_trunks_gettrunkroutes($trunknum) { + global $amp_conf; + + if ($amp_conf["AMPDBENGINE"] == "sqlite3") + $sql_code = "SELECT DISTINCT context, priority FROM extensions WHERE context LIKE 'outrt-%' AND (args LIKE 'dialout-trunk,".$trunknum.",%' OR args LIKE 'dialout-enum,".$trunknum.",%' OR args LIKE 'dialout-dundi,".$trunknum.",%') ORDER BY context"; + else + $sql_code = "SELECT DISTINCT SUBSTRING(context,7), priority FROM extensions WHERE context LIKE 'outrt-%' AND (args LIKE 'dialout-trunk,".$trunknum.",%' OR args LIKE 'dialout-enum,".$trunknum.",%' OR args LIKE 'dialout-dundi,".$trunknum.",%') ORDER BY context"; + + $results = sql( $sql_code, "getAll" ); + + foreach ($results as $row) { + // original code was: + // $routes[$row[0]] = $row[1]; + // but substring is not supported in sqlite3. + // how about we remove the 2nd part of the "if"? and use the same code on all DB's? + + $t = ($amp_conf["AMPDBENGINE"] == "sqlite3") ? substr( $row[0], 7 ) : $row[0]; + $r = $row[1]; + $routes[ $r ] = $t; + + } + // array(routename=>priority) + return isset($routes)?$routes:null; +} + +function core_trunks_deleteDialRules($trunknum) { + $conf = core_trunks_readDialRulesFile(); + + // remove rules for this trunk + unset($conf["trunk-".$trunknum]); + + core_trunks_writeDialRulesFile($conf); +} + +/* end page.trunks.php functions */ + + +/* begin page.routing.php functions */ + +//get unique outbound route names +function core_routing_getroutenames() +{ + global $amp_conf; + + if ($amp_conf["AMPDBENGINE"] == "sqlite3") + { + // SUBSTRING is not supported under sqlite3, we need to filter + // this in php. I am not sure why "6" and not "7" + // but I don't really care -> it works :) + $results = sql("SELECT DISTINCT context FROM extensions WHERE context LIKE 'outrt-%' ORDER BY context ","getAll"); + foreach( array_keys($results) as $idx ) + { + $results[$idx][0] = substr( $results[$idx][0], 6); + } + } + else + { + // we SUBSTRING() to remove "outrt-" + $results = sql("SELECT DISTINCT SUBSTRING(context,7) FROM extensions WHERE context LIKE 'outrt-%' ORDER BY context ","getAll"); + } + + + //TODO: This needs to be yanked, should be in the upgrade script somewhere not here + // + if (count($results) == 0) { + // see if they're still using the old dialprefix method + if ($amp_conf["AMPDBENGINE"] == "sqlite3") { + $sql ="SELECT variable,value FROM globals WHERE variable LIKE 'DIAL\_OUT\_%' ESCAPE '\'"; + } + else { + $sql ="SELECT variable,value FROM globals WHERE variable LIKE 'DIAL\\\_OUT\\\_%'"; + } + $results = sql($sql,"getAll"); + // we SUBSTRING() to remove "outrt-" + + if (count($results) > 0) { + // yes, they are using old method, let's update + + // get the default trunk + $results_def = sql("SELECT value FROM globals WHERE variable = 'OUT'","getAll"); + + if (preg_match("/{OUT_(\d+)}/", $results_def[0][0], $matches)) { + $def_trunk = $matches[1]; + } else { + $def_trunk = ""; + } + + $default_patterns = array( // default patterns that used to be in extensions.conf + "NXXXXXX", + "NXXNXXXXXX", + "1800NXXXXXX", + "1888NXXXXXX", + "1877NXXXXXX", + "1866NXXXXXX", + "1NXXNXXXXXX", + "011.", + "911", + "411", + "311", + ); + + foreach ($results as $temp) { + // temp[0] is "DIAL_OUT_1" + // temp[1] is the dial prefix + + $trunknum = substr($temp[0],9); + + $name = "route".$trunknum; + + $trunks = array(1=>"OUT_".$trunknum); // only one trunk to use + + $patterns = array(); + foreach ($default_patterns as $pattern) { + $patterns[] = $temp[1]."|".$pattern; + } + + if ($trunknum == $def_trunk) { + // this is the default trunk, add the patterns with no prefix + $patterns = array_merge($patterns, $default_patterns); + } + + // add this as a new route + core_routing_add($name, $patterns, $trunks,"new"); + } + + + // delete old values + if ($amp_conf["AMPDBENGINE"] == "sqlite3") { + $sql = "DELETE FROM globals WHERE (variable LIKE 'DIAL\_OUT\_%') ESCAPE '\' OR (variable = 'OUT') "; + } + else { + $sql = "DELETE FROM globals WHERE (variable LIKE 'DIAL\\\_OUT\\\_%') OR (variable = 'OUT') "; + } + sql($sql); + + // we need to re-generate extensions_additional.conf + // i'm not sure how to do this from here + + // re-run our query + $results = sql("SELECT DISTINCT SUBSTRING(context,7) FROM extensions WHERE context LIKE 'outrt-%' ORDER BY context ","getAll"); + // we SUBSTRING() to remove "outrt-" + } + + } // else, it just means they have no routes. + + return $results; +} + +function core_routing_setroutepriority($routepriority, $reporoutedirection, $reporoutekey) +{ + global $db, $amp_conf; + $counter=-1; + foreach ($routepriority as $tresult) + { + $counter++; + if (($counter==($reporoutekey-1)) && ($reporoutedirection=="up")) { + // swap this one with the one before (move up) + $temproute = $routepriority[$counter]; + $routepriority[ $counter ] = $routepriority[ $counter+1 ]; + $routepriority[ $counter+1 ] = $temproute; + + } else if (($counter==($reporoutekey)) && ($reporoutedirection=="down")) { + // swap this one with the one after (move down) + $temproute = $routepriority[ $counter+1 ]; + $routepriority[ $counter+1 ] = $routepriority[ $counter ]; + $routepriority[ $counter ] = $temproute; + } + } + unset($temptrunk); + $routepriority = array_values($routepriority); // resequence our numbers + $counter=0; + foreach ($routepriority as $tresult) { + $order=core_routing_setroutepriorityvalue($counter++); + $sql = sprintf("Update extensions set context='outrt-%s-%s' WHERE context='outrt-%s'",$order,substr($tresult[0],4), $tresult[0]); + $result = $db->query($sql); + if(DB::IsError($result)) { + die_freepbx($result->getMessage()); + } + } + + // Delete and readd the outbound-allroutes entries + $sql = "delete from extensions WHERE context='outbound-allroutes'"; + $result = $db->query($sql); + if(DB::IsError($result)) { + die_freepbx($result->getMessage().$sql); + } + + $sql = "SELECT DISTINCT context FROM extensions WHERE context like 'outrt-%' ORDER BY context"; + $results = $db->getAll($sql); + if(DB::IsError($results)) { + die_freepbx($results->getMessage()); + } + + $priority_loops=1; + foreach ($results as $row) { + $sql = "INSERT INTO extensions (context, extension, priority, application, args, descr, flags) VALUES "; + $sql .= "('outbound-allroutes', "; + $sql .= "'include', "; + $sql .= "'".$priority_loops++."', "; + $sql .= "'".$row[0]."', "; + $sql .= "'', "; + $sql .= "'', "; + $sql .= "'2')"; + + //$sql = sprintf("Update extensions set application='outrt-%s-%s' WHERE context='outbound-allroutes' and application='outrt-%s'",$order,substr($tresult[0],4), $tresult[0]); + $result = $db->query($sql); + if(DB::IsError($result)) { + die_freepbx($result->getMessage(). $sql); + } + } + + if ( $amp_conf["AMPDBENGINE"] == "sqlite3") + $sql = "SELECT DISTINCT context FROM extensions WHERE context LIKE 'outrt-%' ORDER BY context "; + else + $sql = "SELECT DISTINCT SUBSTRING(context,7) FROM extensions WHERE context LIKE 'outrt-%' ORDER BY context "; + + // we SUBSTRING() to remove "outrt-" + $routepriority = $db->getAll($sql); + if(DB::IsError($routepriority)) { + die_freepbx($routepriority->getMessage()); + } + + // TODO: strip the context on the sqlite3 backend + // not sure where does it effects, since this is working on my setup... + // welcome to funky town + return ($routepriority); +} + +function core_routing_setroutepriorityvalue($key) +{ + $key=$key+1; + if ($key<10) + $prefix = sprintf("00%d",$key); + else if ((9<$key)&&($key<100)) + $prefix = sprintf("0%d",$key); + else if ($key>100) + $prefix = sprintf("%d",$key); + return ($prefix); +} + + +function core_routing_add($name, $patterns, $trunks, $method, $pass, $emergency = "", $intracompany = "", $mohsilence = "") { + + global $db; + + $trunktech=array(); + + //Retrieve each trunk tech for later lookup + if ($amp_conf["AMPDBENGINE"] == "sqlite3") { + $sql="select * from globals WHERE variable LIKE 'OUT\_%' ESCAPE '\'"; + } + else { + $sql="select * from globals WHERE variable LIKE 'OUT\\_%'"; + } + $result = $db->getAll($sql); + if(DB::IsError($result)) { + die_freepbx($result->getMessage()); + } + foreach($result as $tr) { + $tech = strtok($tr[1], "/"); + $trunktech[$tr[0]]=$tech; + } + + if ($method=="new") { + $sql="select DISTINCT context FROM extensions WHERE context LIKE 'outrt-%' ORDER BY context"; + $routepriority = $db->getAll($sql); + if(DB::IsError($result)) { + die_freepbx($result->getMessage()); + } + $order=core_routing_setroutepriorityvalue(count($routepriority)); + $name = sprintf ("%s-%s",$order,$name); + } + $trunks = array_values($trunks); // probably already done, but it's important for our dialplan + + + foreach ($patterns as $pattern) { + if (false !== ($pos = strpos($pattern,"|"))) { + // we have a | meaning to not pass the digits on + // (ie, 9|NXXXXXX should use the pattern _9NXXXXXX but only pass NXXXXXX, not the leading 9) + + $pattern = str_replace("|","",$pattern); // remove all |'s + $exten = "EXTEN:".$pos; // chop off leading digit + } else { + // we pass the full dialed number as-is + $exten = "EXTEN"; + } + + if (!preg_match("/^[0-9*]+$/",$pattern)) { + // note # is not here, as asterisk doesn't recoginize it as a normal digit, thus it requires _ pattern matching + + // it's not strictly digits, so it must have patterns, so prepend a _ + $pattern = "_".$pattern; + } + + // 1st priority is emergency dialing variable (if set) + if(!empty($emergency)) { + $startpriority = 1; + $sql = "INSERT INTO extensions (context, extension, priority, application, args, descr) VALUES "; + $sql .= "('outrt-".$name."', "; + $sql .= "'".$pattern."', "; + $sql .= "'".$startpriority."', "; + $sql .= "'SetVar', "; + $sql .= "'EMERGENCYROUTE=YES', "; + $sql .= "'Use Emergency CID for device')"; + $result = $db->query($sql); + if(DB::IsError($result)) { + die_freepbx($result->getMessage()); + } + } else { + $startpriority = 0; + } + + // Next Priority (either first or second depending on above) + if(!empty($intracompany)) { + $startpriority += 1; + $sql = "INSERT INTO extensions (context, extension, priority, application, args, descr) VALUES "; + $sql .= "('outrt-".$name."', "; + $sql .= "'".$pattern."', "; + $sql .= "'".$startpriority."', "; + $sql .= "'SetVar', "; + $sql .= "'INTRACOMPANYROUTE=YES', "; + $sql .= "'Preserve Intenal CID Info')"; + $result = $db->query($sql); + if(DB::IsError($result)) { + die_freepbx($result->getMessage()); + } + } + + // Next Priority (either first, second or third depending on above) + if(!empty($mohsilence) && trim($mohsilence) != 'default') { + $startpriority += 1; + $sql = "INSERT INTO extensions (context, extension, priority, application, args, descr) VALUES "; + $sql .= "('outrt-".$name."', "; + $sql .= "'".$pattern."', "; + $sql .= "'".$startpriority."', "; + $sql .= "'SetVar', "; + $sql .= "'MOHCLASS=".$mohsilence."', "; + $sql .= "'Do not play moh on this route')"; + $result = $db->query($sql); + if(DB::IsError($result)) { + die_freepbx($result->getMessage()); + } + } + + $first_trunk = 1; + foreach ($trunks as $priority => $trunk) { + $priority += $startpriority; + $priority += 1; // since arrays are 0-based, but we want priorities to start at 1 + + $sql = "INSERT INTO extensions (context, extension, priority, application, args) VALUES "; + $sql .= "('outrt-".$name."', "; + $sql .= "'".$pattern."', "; + $sql .= "'".$priority."', "; + $sql .= "'Macro', "; + if ($first_trunk) + $pass_str = $pass; + else + $pass_str = ""; + + if ($trunktech[$trunk] == "ENUM") { + $sql .= "'dialout-enum,".substr($trunk,4).",\${".$exten."},".$pass_str."'"; // cut off OUT_ from $trunk + } else if ($trunktech[$trunk] == "DUNDI") { + $sql .= "'dialout-dundi,".substr($trunk,4).",\${".$exten."},".$pass_str."'"; // cut off OUT_ from $trunk + } else { + $sql .= "'dialout-trunk,".substr($trunk,4).",\${".$exten."},".$pass_str."'"; // cut off OUT_ from $trunk + } + $sql .= ")"; + + $result = $db->query($sql); + if(DB::IsError($result)) { + die_freepbx($result->getMessage()); + } + //To identify the first trunk in a pattern + //so that passwords are in the first trunk in + //each pattern + $first_trunk = 0; + } + + $priority += 1; + $sql = "INSERT INTO extensions (context, extension, priority, application, args, descr) VALUES "; + $sql .= "('outrt-".$name."', "; + $sql .= "'".$pattern."', "; + $sql .= "'".$priority."', "; + $sql .= "'Macro', "; + $sql .= "'outisbusy', "; + $sql .= "'No available circuits')"; + + $result = $db->query($sql); + if(DB::IsError($result)) { + die_freepbx($result->getMessage()); + } + } + + + // add an include=>outrt-$name to [outbound-allroutes]: + + // we have to find the first available priority.. priority doesn't really matter for the include, but + // there is a unique index on (context,extension,priority) so if we don't do this we can't put more than + // one route in the outbound-allroutes context. + $sql = "SELECT priority FROM extensions WHERE context = 'outbound-allroutes' AND extension = 'include'"; + $results = $db->getAll($sql); + if(DB::IsError($results)) { + die_freepbx($results->getMessage()); + } + $priorities = array(); + foreach ($results as $row) { + $priorities[] = $row[0]; + } + for ($priority = 1; in_array($priority, $priorities); $priority++); + + // $priority should now be the lowest available number + + $sql = "INSERT INTO extensions (context, extension, priority, application, args, descr, flags) VALUES "; + $sql .= "('outbound-allroutes', "; + $sql .= "'include', "; + $sql .= "'".$priority."', "; + $sql .= "'outrt-".$name."', "; + $sql .= "'', "; + $sql .= "'', "; + $sql .= "'2')"; + + $result = $db->query($sql); + if(DB::IsError($result)) { + die_freepbx($priority.$result->getMessage()); + } + +} + +function core_routing_edit($name, $patterns, $trunks, $pass, $emergency="", $intracompany = "", $mohsilence="") { + core_routing_del($name); + core_routing_add($name, $patterns, $trunks,"edit", $pass, $emergency, $intracompany, $mohsilence); +} + +function core_routing_del($name) { + global $db; + $sql = "DELETE FROM extensions WHERE context = 'outrt-".$name."'"; + $result = $db->query($sql); + if(DB::IsError($result)) { + die_freepbx($result->getMessage()); + } + + $sql = "DELETE FROM extensions WHERE context = 'outbound-allroutes' AND application = 'outrt-".$name."' "; + $result = $db->query($sql); + if(DB::IsError($result)) { + die_freepbx($result->getMessage()); + } + + return $result; +} + +function core_routing_rename($oldname, $newname) { + global $db; + + $route_prefix=substr($oldname,0,4); + $newname=$route_prefix.$newname; + $sql = "SELECT context FROM extensions WHERE context = 'outrt-".$newname."'"; + $results = $db->getAll($sql); + if (count($results) > 0) { + // there's already a route with this name + return false; + } + + $sql = "UPDATE extensions SET context = 'outrt-".$newname."' WHERE context = 'outrt-".$oldname."'"; + $result = $db->query($sql); + if(DB::IsError($result)) { + die_freepbx($result->getMessage()); + } + $mypriority=sprintf("%d",$route_prefix); + $sql = "UPDATE extensions SET application = 'outrt-".$newname."', priority = '$mypriority' WHERE context = 'outbound-allroutes' AND application = 'outrt-".$oldname."' "; + $result = $db->query($sql); + if(DB::IsError($result)) { + die_freepbx($result->getMessage()); + } + + return true; +} + +//get unique outbound route patterns for a given context +function core_routing_getroutepatterns($route) { + global $db; + $sql = "SELECT extension, args FROM extensions WHERE context = 'outrt-".$route."' AND (args LIKE 'dialout-trunk%' OR args LIKE 'dialout-enum%' OR args LIKE 'dialout-dundi%') ORDER BY extension "; + $results = $db->getAll($sql); + if(DB::IsError($results)) { + die_freepbx($results->getMessage()); + } + + $patterns = array(); + foreach ($results as $row) { + if ($row[0][0] == "_") { + // remove leading _ + $pattern = substr($row[0],1); + } else { + $pattern = $row[0]; + } + + if (preg_match("/{EXTEN:(\d+)}/", $row[1], $matches)) { + // this has a digit offset, we need to insert a | + $pattern = substr($pattern,0,$matches[1])."|".substr($pattern,$matches[1]); + } + + $patterns[] = $pattern; + } + return array_unique($patterns); +} + +//get unique outbound route trunks for a given context +function core_routing_getroutetrunks($route) { + global $db; + $sql = "SELECT DISTINCT args FROM extensions WHERE context = 'outrt-".$route."' AND (args LIKE 'dialout-trunk,%' OR args LIKE 'dialout-enum,%' OR args LIKE 'dialout-dundi,%') ORDER BY CAST(priority as UNSIGNED) "; + $results = $db->getAll($sql); + if(DB::IsError($results)) { + die_freepbx($results->getMessage()); + } + + $trunks = array(); + foreach ($results as $row) { + if (preg_match('/^dialout-trunk,(\d+)/', $row[0], $matches)) { + // check in_array -- even though we did distinct + // we still might get ${EXTEN} and ${EXTEN:1} if they used | to split a pattern + if (!in_array("OUT_".$matches[1], $trunks)) { + $trunks[] = "OUT_".$matches[1]; + } + } else if (preg_match('/^dialout-enum,(\d+)/', $row[0], $matches)) { + if (!in_array("OUT_".$matches[1], $trunks)) { + $trunks[] = "OUT_".$matches[1]; + } + } else if (preg_match('/^dialout-dundi,(\d+)/', $row[0], $matches)) { + if (!in_array("OUT_".$matches[1], $trunks)) { + $trunks[] = "OUT_".$matches[1]; + } + } + } + return $trunks; +} + + +//get password for this route +function core_routing_getroutepassword($route) { + global $db; + $sql = "SELECT DISTINCT args FROM extensions WHERE context = 'outrt-".$route."' AND (args LIKE 'dialout-trunk,%' OR args LIKE 'dialout-enum,%' OR args LIKE 'dialout-dundi,%') ORDER BY CAST(priority as UNSIGNED) "; + $results = $db->getOne($sql); + if(DB::IsError($results)) { + die_freepbx($results->getMessage()); + } + if (preg_match('/^.*,.*,.*,(\d+|\/\S+)/', $results, $matches)) { + $password = $matches[1]; + } else { + $password = ""; + } + + return $password; +} + +//get emergency state for this route +function core_routing_getrouteemergency($route) { + global $db; + $sql = "SELECT DISTINCT args FROM extensions WHERE context = 'outrt-".$route."' AND (args LIKE 'EMERGENCYROUTE%') "; + $results = $db->getOne($sql); + if(DB::IsError($results)) { + die_freepbx($results->getMessage()); + } + if (preg_match('/^.*=(.*)/', $results, $matches)) { + $emergency = $matches[1]; + } else { + $emergency = ""; + } + + return $emergency; +} + +//get intracompany routing status for this route +function core_routing_getrouteintracompany($route) { + + global $db; + $sql = "SELECT DISTINCT args FROM extensions WHERE context = 'outrt-".$route."' AND (args LIKE 'INTRACOMPANYROUTE%') "; + $results = $db->getOne($sql); + if(DB::IsError($results)) { + die_freepbx($results->getMessage()); + } + if (preg_match('/^.*=(.*)/', $results, $matches)) { + $intracompany = $matches[1]; + } else { + $intracompany = ""; + } + return $intracompany; +} + +//get mohsilence routing status for this route +function core_routing_getroutemohsilence($route) { + + global $db; + $sql = "SELECT DISTINCT args FROM extensions WHERE context = 'outrt-".$route."' AND (args LIKE 'MOHCLASS%') "; + $results = $db->getOne($sql); + if(DB::IsError($results)) { + die_freepbx($results->getMessage()); + } + if (preg_match('/^.*=(.*)/', $results, $matches)) { + $mohsilence = $matches[1]; + } else { + $mohsilence = ""; + } + return $mohsilence; +} + +function general_get_zonelist() { + return array( + array ( "name" => "Austria", "iso" => "at", "conf" => "ringcadence = 1000,5000\ndial = 420\nbusy = 420/400,0/400\nring = 420/1000,0/5000\ncongestion = 420/200,0/200\ncallwaiting = 420/40,0/1960\ndialrecall = 420\nrecord = 1400/80,0/14920\ninfo = 950/330,1450/330,1850/330,0/1000\nstutter = 380+420\n"), + array ( "name" => "Australia", "iso" => "au", "conf" => "ringcadence = 400,200,400,2000\ndial = 413+438\nbusy = 425/375,0/375\nring = 413+438/400,0/200,413+438/400,0/2000\ncongestion = 425/375,0/375,420/375,0/375\ncallwaiting = 425/200,0/200,425/200,0/4400\ndialrecall = 413+438\nrecord = !425/1000,!0/15000,425/360,0/15000\ninfo = 425/2500,0/500\nstd = !525/100,!0/100,!525/100,!0/100,!525/100,!0/100,!525/100,!0/100,!525/100\nfacility = 425\nstutter = 413+438/100,0/40\nringmobile = 400+450/400,0/200,400+450/400,0/2000\n"), + array ( "name" => "Brazil", "iso" => "br", "conf" => "ringcadence = 1000,4000\ndial = 425\nbusy = 425/250,0/250\nring = 425/1000,0/4000\ncongestion = 425/250,0/250,425/750,0/250\ncallwaiting = 425/50,0/1000\ndialrecall = 350+440\nrecord = 425/250,0/250\ninfo = 950/330,1400/330,1800/330\nstutter = 350+440\n"), + array ( "name" => "Belgium", "iso" => "be", "conf" => "ringcadence = 1000,3000\ndial = 425\nbusy = 425/500,0/500\nring = 425/1000,0/3000\ncongestion = 425/167,0/167\ncallwaiting = 1400/175,0/175,1400/175,0/3500\ndialrecall = !350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,350+440\nrecord = 1400/500,0/15000\ninfo = 900/330,1400/330,1800/330,0/1000\nstutter = 425/1000,0/250\n"), + array ( "name" => "Switzerland", "iso" => "ch", "conf" => "ringcadence = 1000,4000\ndial = 425\nbusy = 425/500,0/500\nring = 425/1000,0/4000\ncongestion = 425/200,0/200\ncallwaiting = 425/200,0/200,425/200,0/4000\ndialrecall = !425/100,!0/100,!425/100,!0/100,!425/100,!0/100,425\nrecord = 1400/80,0/15000\ninfo = 950/330,1400/330,1800/330,0/1000\nstutter = 425+340/1100,0/1100\n"), + array ( "name" => "Chile", "iso" => "cl", "conf" => "ringcadence = 1000,3000\ndial = 400\nbusy = 400/500,0/500\nring = 400/1000,0/3000\ncongestion = 400/200,0/200\ncallwaiting = 400/250,0/8750\ndialrecall = !400/100,!0/100,!400/100,!0/100,!400/100,!0/100,400\nrecord = 1400/500,0/15000\ninfo = 950/333,1400/333,1800/333,0/1000\nstutter = !400/100,!0/100,!400/100,!0/100,!400/100,!0/100,!400/100,!0/100,!400/100,!0/100,!400/100,!0/100,400\n"), + array ( "name" => "China", "iso" => "cn", "conf" => "ringcadence = 1000,4000\ndial = 450\nbusy = 450/350,0/350\nring = 450/1000,0/4000\ncongestion = 450/700,0/700\ncallwaiting = 450/400,0/4000\ndialrecall = 450\nrecord = 950/400,0/10000\ninfo = 450/100,0/100,450/100,0/100,450/100,0/100,450/400,0/400\nstutter = 450+425\n"), + array ( "name" => "Czech Republic", "iso" => "cz", "conf" => "ringcadence = 1000,4000\ndial = 425/330,0/330,425/660,0/660\nbusy = 425/330,0/330\nring = 425/1000,0/4000\ncongestion = 425/165,0/165\ncallwaiting = 425/330,0/9000\ndialrecall = !425/100,!0/100,!425/100,!0/100,!425/100,!0/100,425/330,0/330,425/660,0/660\nrecord = 1400/500,0/14000\ninfo = 950/330,0/30,1400/330,0/30,1800/330,0/1000\nstutter = 425/450,0/50\n"), + array ( "name" => "Germany", "iso" => "de", "conf" => "ringcadence = 1000,4000\ndial = 425\nbusy = 425/480,0/480\nring = 425/1000,0/4000\ncongestion = 425/240,0/240\ncallwaiting = !425/200,!0/200,!425/200,!0/5000,!425/200,!0/200,!425/200,!0/5000,!425/200,!0/200,!425/200,!0/5000,!425/200,!0/200,!425/200,!0/5000,!425/200,!0/200,!425/200,0\ndialrecall = !425/100,!0/100,!425/100,!0/100,!425/100,!0/100,425\nrecord = 1400/80,0/15000\ninfo = 950/330,1400/330,1800/330,0/1000\nstutter = 425+400\n"), + array ( "name" => "Denmark", "iso" => "dk", "conf" => "ringcadence = 1000,4000\ndial = 425\nbusy = 425/500,0/500\nring = 425/1000,0/4000\ncongestion = 425/200,0/200\ncallwaiting = !425/200,!0/600,!425/200,!0/3000,!425/200,!0/200,!425/200,0\ndialrecall = !425/100,!0/100,!425/100,!0/100,!425/100,!0/100,425\nrecord = 1400/80,0/15000\ninfo = 950/330,1400/330,1800/330,0/1000\nstutter = 425/450,0/50\n"), + array ( "name" => "Estonia", "iso" => "ee", "conf" => "ringcadence = 1000,4000\ndial = 425\nbusy = 425/300,0/300\nring = 425/1000,0/4000\ncongestion = 425/200,0/200\ncallwaiting = 950/650,0/325,950/325,0/30,1400/1300,0/2600\ndialrecall = 425/650,0/25\nrecord = 1400/500,0/15000\ninfo = 950/650,0/325,950/325,0/30,1400/1300,0/2600\nstutter = !425/100,!0/100,!425/100,!0/100,!425/100,!0/100,!425/100,!0/100,!425/100,!0/100,!425/100,!0/100,425\n"), + array ( "name" => "Spain", "iso" => "es", "conf" => "ringcadence = 1500,3000\ndial = 425\nbusy = 425/200,0/200\nring = 425/1500,0/3000\ncongestion = 425/200,0/200,425/200,0/200,425/200,0/600\ncallwaiting = 425/175,0/175,425/175,0/3500\ndialrecall = !425/200,!0/200,!425/200,!0/200,!425/200,!0/200,425\nrecord = 1400/500,0/15000\ninfo = 950/330,0/1000\ndialout = 500\n\n"), + array ( "name" => "Finland", "iso" => "fi", "conf" => "ringcadence = 1000,4000\ndial = 425\nbusy = 425/300,0/300\nring = 425/1000,0/4000\ncongestion = 425/200,0/200\ncallwaiting = 425/150,0/150,425/150,0/8000\ndialrecall = 425/650,0/25\nrecord = 1400/500,0/15000\ninfo = 950/650,0/325,950/325,0/30,1400/1300,0/2600\nstutter = 425/650,0/25\n"), + array ( "name" => "France", "iso" => "fr", "conf" => "ringcadence = 1500,3500\ndial = 440\nbusy = 440/500,0/500\nring = 440/1500,0/3500\ncongestion = 440/250,0/250\ncallwait = 440/300,0/10000\ndialrecall = !350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,350+440\nrecord = 1400/500,0/15000\ninfo = !950/330,!1400/330,!1800/330\nstutter = !440/100,!0/100,!440/100,!0/100,!440/100,!0/100,!440/100,!0/100,!440/100,!0/100,!440/100,!0/100,440\n"), + array ( "name" => "Greece", "iso" => "gr", "conf" => "ringcadence = 1000,4000\ndial = 425/200,0/300,425/700,0/800\nbusy = 425/300,0/300\nring = 425/1000,0/4000\ncongestion = 425/200,0/200\ncallwaiting = 425/150,0/150,425/150,0/8000\ndialrecall = 425/650,0/25\nrecord = 1400/400,0/15000\ninfo = !950/330,!1400/330,!1800/330,!0/1000,!950/330,!1400/330,!1800/330,!0/1000,!950/330,!1400/330,!1800/330,!0/1000,0\nstutter = 425/650,0/25\n"), +array ( "name" => "Hong Kong", "iso" => "hk", "conf" => "ringcadence = 400,200,400,3000\ndial = 350+440\nbusy = 480+620/500,0/500\nring = 440+480/400,0/200,440+480/400,0/3000\ncongestion = 480+620/250,0/250\ncallwaiting = 440/300,0/10000\ndialrecall = !350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,350+440\nrecord = 1400/500,0/15000\ninfo = !950/330,!1400/330,!1800/330,0\nstutter = !350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,350+440\n"), + array ( "name" => "Hungary", "iso" => "hu", "conf" => "ringcadence = 1250,3750\ndial = 425\nbusy = 425/300,0/300\nring = 425/1250,0/3750\ncongestion = 425/300,0/300\ncallwaiting = 425/40,0/1960\ndialrecall = 425+450\nrecord = 1400/400,0/15000\ninfo = !950/330,!1400/330,!1800/330,!0/1000,!950/330,!1400/330,!1800/330,!0/1000,!950/330,!1400/330,!1800/330,!0/1000,0\nstutter = 350+375+400\n"), + array ( "name" => "India", "iso" => "in", "conf" => "ringcadence = 400,200,400,2000\ndial = 400*25\nbusy = 400/750,0/750\nring = 400*25/400,0/200,400*25/400,0/2000\ncongestion = 400/250,0/250\ncallwaiting = 400/200,0/100,400/200,0/7500\ndialrecall = !350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,350+440\nrecord = 1400/500,0/15000\ninfo = !950/330,!1400/330,!1800/330,0/1000\nstutter = !350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,350+440\n"), + array ( "name" => "Israel", "iso" => "il", "conf" => "ringcadence = 1000,3000\ndial = 414\nbusy = 414/500,0/500\nring = 414/1000,0/3000\ncongestion = 414/250,0/250\ncallwaiting = 414/100,0/100,414/100,0/100,414/600,0/3000 \ndialrecall = !414/100,!0/100,!414/100,!0/100,!414/100,!0/100,414\nrecord = 1400/500,0/15000\ninfo = 1000/330,1400/330,1800/330,0/1000\nstutter = !414/160,!0/160,!414/160,!0/160,!414/160,!0/160,!414/160,!0/160,!414/160,!0/160,!414/160,!0/160,!414/160,!0/160,!414/160,!0/160,!414/160,!0/160,!414/160,!0/160,414 \n"), + array ( "name" => "Italy", "iso" => "it", "conf" => "ringcadence = 1000,4000\ndial = 425/200,0/200,425/600,0/1000\nbusy = 425/500,0/500\nring = 425/1000,0/4000\ncongestion = 425/200,0/200\ncallwaiting = 425/400,0/100,425/250,0/100,425/150,0/14000\ndialrecall = 470/400,425/400\nrecord = 1400/400,0/15000\ninfo = !950/330,!1400/330,!1800/330,!0/1000,!950/330,!1400/330,!1800/330,!0/1000,!950/330,!1400/330,!1800/330,!0/1000,0\nstutter = 470/400,425/400\n"), + array ( "name" => "Japan", "iso" => "jp", "conf" => "ringcadence = 1000,2000\ndial = 400\nbusy = 400/500,0/500\nring = 400+15/1000,0/2000\ncongestion = 400/500,0/500\ncallwaiting = 400+16/500,0/8000\ndialrecall = !400/200,!0/200,!400/200,!0/200,!400/200,!0/200,400\nrecord = 1400/500,0/15000\ninfo = !950/330,!1400/330,!1800/330,0\nstutter = !400/100,!0/100,!400/100,!0/100,!400/100,!0/100,!400/100,!0/100,!400/100,!0/100,!400/100,!0/100,400\n"), + array ( "name" => "Lithuania", "iso" => "lt", "conf" => "ringcadence = 1000,4000\ndial = 425\nbusy = 425/350,0/350\nring = 425/1000,0/4000\ncongestion = 425/200,0/200\ncallwaiting = 425/150,0/150,425/150,0/4000\ndialrecall = 425/500,0/50\nrecord = 1400/500,0/15000\ninfo = !950/330,!1400/330,!1800/330,!0/1000,!950/330,!1400/330,!1800/330,!0/1000,!950/330,!1400/330,!1800/330,!0/1000,0\nstutter = !425/100,!0/100,!425/100,!0/100,!425/100,!0/100,!425/100,!0/100,!425/100,!0/100,!425/100,!0/100,425\n"), + array ( "name" => "Mexico", "iso" => "mx", "conf" => "ringcadence = 2000,4000\ndial = 425\nbusy = 425/250,0/250\nring = 425/1000,0/4000\ncongestion = 425/250,0/250\ncallwaiting = 425/200,0/600,425/200,0/10000\ndialrecall = !350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,350+440\nrecord = 1400/500,0/15000\ninfo = 950/330,0/30,1400/330,0/30,1800/330,0/1000\nstutter = !350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,350+440\n"), + array ( "name" => "Netherlands", "iso" => "nl", "conf" => "ringcadence = 1000,4000\ndial = 425\nbusy = 425/500,0/500\nring = 425/1000,0/4000\ncongestion = 425/250,0/250\ncallwaiting = 425/500,0/9500\ndialrecall = 425/500,0/50\nrecord = 1400/500,0/15000\ninfo = 950/330,1400/330,1800/330,0/1000\nstutter = 425/500,0/50\n"), + array ( "name" => "Norway", "iso" => "no", "conf" => "ringcadence = 1000,4000\ndial = 425\nbusy = 425/500,0/500\nring = 425/1000,0/4000\ncongestion = 425/200,0/200\ncallwaiting = 425/200,0/600,425/200,0/10000\ndialrecall = 470/400,425/400\nrecord = 1400/400,0/15000\ninfo = !950/330,!1400/330,!1800/330,!0/1000,!950/330,!1400/330,!1800/330,!0/1000,!950/330,!1400/330,!1800/330,!0/1000,0\nstutter = 470/400,425/400\n"), + array ( "name" => "New Zealand", "iso" => "nz", "conf" => "ringcadence = 400,200,400,2000\ndial = 400\nbusy = 400/250,0/250\nring = 400+450/400,0/200,400+450/400,0/2000\ncongestion = 400/375,0/375\ncallwaiting = !400/200,!0/3000,!400/200,!0/3000,!400/200,!0/3000,!400/200\ndialrecall = !400/100!0/100,!400/100,!0/100,!400/100,!0/100,400\nrecord = 1400/425,0/15000\ninfo = 400/750,0/100,400/750,0/100,400/750,0/100,400/750,0/400\nstutter = !400/100!0/100,!400/100,!0/100,!400/100,!0/100,!400/100!0/100,!400/100,!0/100,!400/100,!0/100,400\n"), + array ( "name" => "Philippines", "iso" => "phl", "conf" => "ringcadence = 1000,4000\ndial = 425\nbusy = 480+620/500,0/500\nring = 425+480/1000,0/4000\ncongestion = 480+620/250,0/250\ncallwaiting = 440/300,0/10000\ndialrecall = !350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,350+440\nrecord = 1400/500,0/15000\ninfo = !950/330,!1400/330,!1800/330,0\nstutter = !350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,350+440\n"), + array ( "name" => "Poland", "iso" => "pl", "conf" => "ringcadence = 1000,4000\ndial = 425\nbusy = 425/500,0/500\nring = 425/1000,0/4000\ncongestion = 425/500,0/500\ncallwaiting = 425/150,0/150,425/150,0/4000\ndialrecall = 425/500,0/50\nrecord = 1400/500,0/15000\ninfo = !950/330,!1400/330,!1800/330,!0/1000,!950/330,!1400/330,!1800/330,!0/1000,!950/330,!1400/330,!1800/330,!0/1000\nstutter = !425/100,!0/100,!425/100,!0/100,!425/100,!0/100,!425/100,!0/100,!425/100,!0/100,!425/100,!0/100,425\n"), + array ( "name" => "Portugal", "iso" => "pt", "conf" => "ringcadence = 1000,5000\ndial = 425\nbusy = 425/500,0/500\nring = 425/1000,0/5000\ncongestion = 425/200,0/200\ncallwaiting = 440/300,0/10000\ndialrecall = 425/1000,0/200\nrecord = 1400/500,0/15000\ninfo = 950/330,1400/330,1800/330,0/1000\nstutter = !425/100,!0/100,!425/100,!0/100,!425/100,!0/100,!425/100,!0/100,!425/100,!0/100,!425/100,!0/100,425\n"), + array ( "name" => "Russia / ex Soviet Union", "iso" => "ru", "conf" => "ringcadence = 800,3200\ndial = 425\nbusy = 425/350,0/350\nring = 425/800,0/3200\ncongestion = 425/350,0/350\ncallwaiting = 425/200,0/5000\ndialrecall = !350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,350+440\nrecord = 1400/500,0/15000\ninfo = !950/330,!1400/330,!1800/330,0\n"), + array ( "name" => "Singapore", "iso" => "sg", "conf" => "ringcadence = 400,200,400,2000\ndial = 425\nring = 425*24/400,0/200,425*24/400,0/2000 ; modulation should be 100%, not 90%\nbusy = 425/750,0/750\ncongestion = 425/250,0/250\ncallwaiting = 425*24/300,0/200,425*24/300,0/3200\nstutter = !425/200,!0/200,!425/600,!0/200,!425/200,!0/200,!425/600,!0/200,!425/200,!0/200,!425/600,!0/200,!425/200,!0/200,!425/600,!0/200,425\ninfo = 950/330,1400/330,1800/330,0/1000 ; not currently in use acc. to reference\ndialrecall = 425*24/500,0/500,425/500,0/2500 ; unspecified in IDA reference, use repeating Holding Tone A,B\nrecord = 1400/500,0/15000 ; unspecified in IDA reference, use 0.5s tone every 15s\nnutone = 425/2500,0/500\nintrusion = 425/250,0/2000\nwarning = 425/624,0/4376 ; end of period tone, warning\nacceptance = 425/125,0/125\nholdinga = !425*24/500,!0/500 ; followed by holdingb\nholdingb = !425/500,!0/2500\n"), + array ( "name" => "South Africa", "iso" => "za", "conf" => "ringcadence = 400,200,400,2000\ndial = 400*33\nbusy = 400/500,0/500\nring = 400*33/400,0/200,400*33/400,0/2000\ncongestion = 400/250,0/250\ncallwaiting = 400*33/250,0/250,400*33/250,0/250,400*33/250,0/250,400*33/250,0/250\ndialrecall = 350+440\nrecord = 1400/500,0/10000\ninfo = 950/330,1400/330,1800/330,0/330\nstutter =!400*33/100,!0/100,!400*33/100,!0/100,!400*33/100,!0/100,!400*33/100,!0/100,!400*33/100,!0/100,!400*33/100,!0/100,400*33 \n"), + array ( "name" => "Sweden", "iso" => "se", "conf" => "ringcadence = 1000,5000\ndial = 425\nbusy = 425/250,0/250\nring = 425/1000,0/5000\ncongestion = 425/250,0/750\ncallwaiting = 425/200,0/500,425/200,0/9100\ndialrecall = !425/100,!0/100,!425/100,!0/100,!425/100,!0/100,425\nrecord = 1400/500,0/15000\ninfo = !950/332,!0/24,!1400/332,!0/24,!1800/332,!0/2024,!950/332,!0/24,!1400/332,!0/24,!1800/332,!0/2024,!950/332,!0/24,!1400/332,!0/24,!1800/332,!0/2024,!950/332,!0/24,!1400/332,!0/24,!1800/332,!0/2024,!950/332,!0/24,!1400/332,!0/24,!1800/332,0\nstutter = !425/100,!0/100,!425/100,!0/100,!425/100,!0/100,!425/100,!0/100,!425/100,!0/100,!425/100,!0/100,425\n"), + array ("name" => "Turkey", "iso" => "tr", "conf" => "ringcadance = 2000,4000\ndial = 450\nbusy = 450/500,0/500\nring = 450/2000,450/4000\ncongestion = 450/200,0/200,450/200,0/200,450/200,0/200,450/600,0/200\ncallwaiting = 450/200,0/600,450/200,0/8000\ndialrecall = 450/1000,0/250\nrecord = 1400/500,0/15000\ninfo = !950/300,!1400/300,!1800/300,!0/1000,!950/300,!1400/300,!1800/300,!0/1000,!950/300,!1400/300,!1800/300,!0/1000,0\n"), + array ( "name" => "United Kingdom", "iso" => "uk", "conf" => "ringcadence = 400,200,400,2000\ndial = 350+440\nspecialdial = 350+440/750,440/750\nbusy = 400/375,0/375\ncongestion = 400/400,0/350,400/225,0/525\nspecialcongestion = 400/200,1004/300\nunobtainable = 400\nring = 400+450/400,0/200,400+450/400,0/2000\ncallwaiting = 400/100,0/4000\nspecialcallwaiting = 400/250,0/250,400/250,0/250,400/250,0/5000\ncreditexpired = 400/125,0/125\nconfirm = 1400\nswitching = 400/200,0/400,400/2000,0/400\ninfo = 950/330,0/15,1400/330,0/15,1800/330,0/1000\nrecord = 1400/500,0/60000\nstutter = 350+440/750,440/750\n"), + array ( "name" => "United States / North America", "iso" => "us", "conf" => "ringcadence = 2000,4000\ndial = 350+440\nbusy = 480+620/500,0/500\nring = 440+480/2000,0/4000\ncongestion = 480+620/250,0/250\ncallwaiting = 440/300,0/10000\ndialrecall = !350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,350+440\nrecord = 1400/500,0/15000\ninfo = !950/330,!1400/330,!1800/330,0\nstutter = !350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,350+440\n"), + array ( "name" => "United States Circa 1950/ North America", "iso" => "us-old", "conf" => "ringcadence = 2000,4000\ndial = 600*120\nbusy = 500*100/500,0/500\nring = 420*40/2000,0/4000\ncongestion = 500*100/250,0/250\ncallwaiting = 440/300,0/10000\ndialrecall = !600*120/100,!0/100,!600*120/100,!0/100,!600*120/100,!0/100,600*120\nrecord = 1400/500,0/15000\ninfo = !950/330,!1400/330,!1800/330,0\nstutter = !600*120/100,!0/100,!600*120/100,!0/100,!600*120/100,!0/100,!600*120/100,!0/100,!600*120/100,!0/100,!600*120/100,!0/100,600*120\n"), + array ( "name" => "Taiwan", "iso" => "tw", "conf" => "ringcadence = 1000,4000\ndial = 350+440\nbusy = 480+620/500,0/500\nring = 440+480/1000,0/2000\ncongestion = 480+620/250,0/250\ncallwaiting = 350+440/250,0/250,350+440/250,0/3250\ndialrecall = 300/1500,0/500\nrecord = 1400/500,0/15000\ninfo = !950/330,!1400/330,!1800/330,0\nstutter = !350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,350+440\n"), + array ( "name" => "Venezuela / South America", "iso" => "ve", "conf" => "; Tone definition source for ve found on\n; Reference: http://www.itu.int/ITU-T/inr/forms/files/tones-0203.pdf\nringcadence = 1000,4000\ndial = 425\nbusy = 425/500,0/500\nring = 425/1000,0/4000\ncongestion = 425/250,0/250\ncallwaiting = 400+450/300,0/6000\ndialrecall = 425\nrecord = 1400/500,0/15000\ninfo = !950/330,!1440/330,!1800/330,0/1000\n"),); +} + +function general_display_zones($curzone) { + $zonelist = general_get_zonelist(); + echo ""; + +} + +function general_generate_indications() { + global $db; + global $asterisk_conf; + + $notify =& notifications::create($db); + + $sql = "SELECT value FROM globals WHERE variable='TONEZONE'"; + $result = $db->getRow($sql,DB_FETCHMODE_ASSOC); + + $filename = isset($asterisk_conf["astetcdir"]) && $asterisk_conf["astetcdir"] != '' ? rtrim($asterisk_conf["astetcdir"],DIRECTORY_SEPARATOR) : "/etc/asterisk"; + $filename .= "/indications.conf"; + + if (($fd = fopen($filename, "w")) === false) { + $desc = sprintf(_("Failed to open %s for writing, aborting attempt to write the country indications. The file may be readonly or the permissions may be incorrect."), $filename); + $notify->add_error('core','INDICATIONS',_("Failed to write indications.conf"), $desc); + return; + } + $notify->delete('core', 'INDICATIONS'); + + fwrite($fd, "[general]\ncountry=".$result['value']."\n\n"); + + $zonelist = general_get_zonelist(); + foreach ($zonelist as $zone) { + fwrite($fd, "[{$zone['iso']}]\n{$zone['conf']}\n\n"); + } + fclose($fd); +} +/* end page.routing.php functions */ + + + +// init registered 'your' config load and config process functions +function core_users_configpageinit($dispnum) { + global $currentcomponent; + global $amp_conf; + + if ( $dispnum == 'users' || $dispnum == 'extensions' ) { + // Setup option list we need + $currentcomponent->addoptlistitem('recordoptions', 'Adhoc', _("On Demand")); + $currentcomponent->addoptlistitem('recordoptions', 'Always', _("Always")); + $currentcomponent->addoptlistitem('recordoptions', 'Never', _("Never")); + $currentcomponent->setoptlistopts('recordoptions', 'sort', false); + + $currentcomponent->addoptlistitem('callwaiting', 'enabled', _("Enable")); + $currentcomponent->addoptlistitem('callwaiting', 'disabled', _("Disable")); + $currentcomponent->setoptlistopts('callwaiting', 'sort', false); + + $currentcomponent->addoptlistitem('call_screen', '0', _("Disable")); + $currentcomponent->addoptlistitem('call_screen', 'nomemory', _("Screen Caller: No Memory")); + $currentcomponent->addoptlistitem('call_screen', 'memory', _("Screen Caller: Memory")); + $currentcomponent->setoptlistopts('call_screen', 'sort', false); + + $currentcomponent->addoptlistitem('ringtime', '0', 'Default'); + for ($i=1; $i <= 120; $i++) { + $currentcomponent->addoptlistitem('ringtime', "$i", "$i"); + } + $currentcomponent->setoptlistopts('ringtime', 'sort', false); + + // Special CID handling to deal with Private, etc. + // + $js = + 'var mycid = thiscid.toLowerCase(); + if (isDialpattern(thiscid) || mycid.substring(0,4) == "priv" || mycid.substring(0,5) == "block" || mycid == "unknown" || mycid.substring(0,8) == "restrict" || mycid.substring(0,7) == "unavail" || mycid.substring(0,6) == "anonym") { return true } else { return false }; + '; + $currentcomponent->addjsfunc('isValidCID(thiscid)', $js); + + // Add the 'proces' functions + $currentcomponent->addguifunc('core_users_configpageload'); + // Ensure users is called in middle order ($sortorder = 5), this is to allow + // other modules to call stuff before / after the processing of users if needed + // e.g. Voicemail module needs to create mailbox BEFORE the users as the mailbox + // context is needed by the add users function + $currentcomponent->addprocessfunc('core_users_configprocess', 5); + } +} + +// Used below in usort +function dev_grp($a, $b) { + if ($a['devicetype'] == $b['devicetype']) { + return ($a['id'] < $b['id']) ? -1 : 1; + } else { + return ($a['devicetype'] > $b['devicetype']) ? -1 : 1; + } +} + +function core_users_configpageload() { + global $currentcomponent; + global $amp_conf; + + // Ensure variables possibly extracted later exist + $name = $outboundcid = $record_in = $record_out = $sipname = $cid_masquerade = $newdid_name = $newdid = $newdidcid = null; + + // Init vars from $_REQUEST[] + $display = isset($_REQUEST['display'])?$_REQUEST['display']:null;; + $action = isset($_REQUEST['action'])?$_REQUEST['action']:null; + $extdisplay = isset($_REQUEST['extdisplay'])?$_REQUEST['extdisplay']:null; + $tech_hardware = isset($_REQUEST['tech_hardware'])?$_REQUEST['tech_hardware']:null; + + if ( $action == 'del' ) { // Deleted + + $currentcomponent->addguielem('_top', new gui_subheading('del', $extdisplay.' '._("deleted"), false)); + + } elseif ( $display == 'extensions' && ($extdisplay == '' && $tech_hardware == '') ) { // Adding + + // do nothing as you want the Devices to handle this bit + + } else { + + $delURL = $_SERVER['PHP_SELF'].'?'.$_SERVER['QUERY_STRING'].'&action=del'; + + if ( is_string($extdisplay) ) { + + if (!isset($GLOBALS['abort']) || $GLOBALS['abort'] !== true) { + $extenInfo=core_users_get($extdisplay); + extract($extenInfo); + } + if (isset($deviceInfo) && is_array($deviceInfo)) + extract($deviceInfo); + + if ( $display == 'extensions' ) { + $currentcomponent->addguielem('_top', new gui_pageheading('title', _("Extension").": $extdisplay", false), 0); + if (!isset($GLOBALS['abort']) || $GLOBALS['abort'] !== true) { + $tlabel = sprintf(_("Delete Extension %s"),$extdisplay); + $label = ' '.$tlabel.''; + $currentcomponent->addguielem('_top', new gui_link('del', $label, $delURL, true, false), 0); + + $usage_list = framework_display_destination_usage(core_getdest($extdisplay)); + if (!empty($usage_list)) { + $currentcomponent->addguielem('_top', new gui_link_label('dests', $usage_list['text'], $usage_list['tooltip'], true), 0); + } + } + } else { + $currentcomponent->addguielem('_top', new gui_pageheading('title', _("User").": $extdisplay", false), 0); + if (!isset($GLOBALS['abort']) || $GLOBALS['abort'] !== true) { + $tlabel = sprintf(_("Delete User %s"),$extdisplay); + $label = ' '.$tlabel.''; + $currentcomponent->addguielem('_top', new gui_link('del', $label, $delURL, true, false), 0); + + $usage_list = framework_display_destination_usage(core_getdest($extdisplay)); + if (!empty($usage_list)) { + $currentcomponent->addguielem('_top', new gui_link_label('dests', $usage_list['text'], $usage_list['tooltip'], true), 0); + } + } + } + + } elseif ( $display != 'extensions' ) { + $currentcomponent->addguielem('_top', new gui_pageheading('title', _("Add User/Extension")), 0); + } + + // Setup vars for use in the gui later on + $fc_logon = featurecodes_getFeatureCode('core', 'userlogon'); + $fc_logoff = featurecodes_getFeatureCode('core', 'userlogoff'); + + $msgInvalidExtNum = _("Please enter a valid extension number."); + $msgInvalidCidNum = _("Please enter a valid CID Num Alias (must be a valid number)."); + $msgInvalidExtPwd = _("Please enter valid User Password using numbers only"); + $msgInvalidDispName = _("Please enter a valid Display Name"); + $msgInvalidOutboundCID = _("Please enter a valid Outbound CID"); + $msgInvalidPause = _("Please enter a valid pause time in seconds, using digits only"); + $msgInvalidDIDNum = _("You have entered a non-standard dialpattern for your DID. You can only enter standard dialpatterns. You must use the inbound routing form to enter non-standard patterns"); + $msgInvalidCIDNum = _("Please enter a valid Caller ID Number or leave it blank for your Assigned DID/CID pair"); + + // This is the actual gui stuff + $currentcomponent->addguielem('_top', new gui_hidden('action', ($extdisplay ? 'edit' : 'add'))); + $currentcomponent->addguielem('_top', new gui_hidden('extdisplay', $extdisplay)); + + if ( $display == 'extensions' ) { + $section = ($extdisplay ? _("Edit Extension") : _("Add Extension")); + } else { + $section = ($extdisplay ? _("Edit User") : _("Add User")); + } + if ( trim($extdisplay) != '' ) { + $currentcomponent->addguielem($section, new gui_hidden('extension', $extdisplay), 2); + } else { + $currentcomponent->addguielem($section, new gui_textbox('extension', $extdisplay, _("User Extension"), _("The extension number to dial to reach this user."), '!isInteger()', $msgInvalidExtNum, false), 3); + } + if ( $display != 'extensions' ) { + $currentcomponent->addguielem($section, new gui_password('password', $password, _("User Password"), _("A user will enter this password when logging onto a device.").' '.$fc_logon.' '._("logs into a device.").' '.$fc_logoff.' '._("logs out of a device."), '!isInteger() && !isWhitespace()', $msgInvalidExtPwd, true)); + // extra JS function check required for blank password warning -- call last in the onsubmit() function + $currentcomponent->addjsfunc('onsubmit()', "\treturn checkBlankUserPwd();\n", 9); + } + $currentcomponent->addguielem($section, new gui_textbox('name', $name, _("Display Name"), _("The caller id name for calls from this user will be set to this name. Only enter the name, NOT the number."), '!isAlphanumeric() || isWhitespace()', $msgInvalidDispName, false)); + $cid_masquerade = (trim($cid_masquerade) == $extdisplay)?"":$cid_masquerade; + $currentcomponent->addguielem($section, new gui_textbox('cid_masquerade', $cid_masquerade, _("CID Num Alias"), _("The CID Number to use for internal calls, if different from the extension number. This is used to masquerade as a different user. A common example is a team of support people who would like their internal callerid to display the general support number (a ringgroup or queue). There will be no effect on external calls."), '!isWhitespace() && !isInteger()', $msgInvalidCidNum, false)); + $currentcomponent->addguielem($section, new gui_textbox('sipname', $sipname, _("SIP Alias"), _("If you want to support direct sip dialing of users internally or through anonymous sip calls, you can supply a friendly name that can be used in addition to the users extension to call them."))); + + // If user mode, list devices associated with this user + // + if ($display == 'users' && trim($extdisplay != '')) { + $section = _("User Devices"); + $device_list = core_devices_list('all','full'); + + usort($device_list,'dev_grp'); + + $link_count = 0; + foreach ($device_list as $device_item) { + if ($device_item['user'] == $extdisplay) { + $editURL = $_SERVER['PHP_SELF'].'?type=setup&display=devices&skip=0&extdisplay='.$device_item['id']; + $device_icon = ($device_item['devicetype'] == 'fixed') ? 'images/telephone_key.png' : 'images/telephone_edit.png'; + $device_label = ' '; + $device_label .= _('Edit:'); + $device_label .= ' '.$device_item['id'].' '.$device_item['description']; + + $device_label = ' + Edit Device'.$device_label. + ' '; + + $currentcomponent->addguielem($section, new gui_link('dev'.$link_count++, $device_label, $editURL, true, false), 2); + } + } + } + + $section = _("Extension Options"); + $currentcomponent->addguielem($section, new gui_textbox('outboundcid', $outboundcid, _("Outbound CID"), _("Overrides the caller id when dialing out a trunk. Any setting here will override the common outbound caller id set in the Trunks admin.

Format: \"caller name\" <#######>

Leave this field blank to disable the outbound callerid feature for this user."), '!isCallerID()', $msgInvalidOutboundCID, true),3); + $ringtimer = (isset($ringtimer) ? $ringtimer : '0'); + $currentcomponent->addguielem($section, new gui_selectbox('ringtimer', $currentcomponent->getoptlist('ringtime'), $ringtimer, _("Ring Time"), _("Number of seconds to ring prior to going to voicemail. Default will use the value set in the General Tab. If no voicemail is configured this will be ignored."), false)); + if (!isset($callwaiting)) { + if ($amp_conf['ENABLECW']) { + $callwaiting = 'enabled'; + } else { + $callwaiting = 'disabled'; + } + } + $currentcomponent->addguielem($section, new gui_selectbox('callwaiting', $currentcomponent->getoptlist('callwaiting'), $callwaiting, _("Call Waiting"), _("Set the initial/current Call Waiting state for this user's extension"), false)); + $currentcomponent->addguielem($section, new gui_selectbox('call_screen', $currentcomponent->getoptlist('call_screen'), $call_screen, _("Call Screening"),_("Call Screening requires external callers to say their name, which will be played back to the user and allow the user to accept or reject the call. Screening with memory only verifies a caller for their caller-id once. Screening without memory always requires a caller to say their name. Either mode will always announce the caller based on the last introduction saved with that callerid. If any user on the system uses the memory option, when that user is called, the caller will be required to re-introduce themselves and all users on the system will have that new introduction associated with the caller's CallerId."), false)); + + $section = _("Assigned DID/CID"); + $currentcomponent->addguielem($section, new gui_textbox('newdid_name', $newdid_name, _("DID Description"), _("A description for this DID, such as \"Fax\"")), 4); + $currentcomponent->addguielem($section, new gui_textbox('newdid', $newdid, _("Add Inbound DID"), _("A direct DID that is associated with this extension. The DID should be in the same format as provided by the provider (e.g. full number, 4 digits for 10x4, etc).

Format should be: XXXXXXXXXX

.An optional CID can also be associated with this DID by setting the next box"),'!isDialpattern()',$msgInvalidDIDNum,true), 4); + $currentcomponent->addguielem($section, new gui_textbox('newdidcid', $newdidcid, _("Add Inbound CID"), _("Add a CID for more specific DID + CID routing. A DID must be specified in the above Add DID box. In addition to standard dial sequences, you can also put Private, Blocked, Unknown, Restricted, Anonymous and Unavailable in order to catch these special cases if the Telco transmits them."),"!frm_${display}_isValidCID()",$msgInvalidCIDNum,true), 4); + + $dids = core_did_list('extension'); + $did_count = 0; + foreach ($dids as $did) { + $did_dest = split(',',$did['destination']); + if (isset($did_dest[1]) && $did_dest[1] == $extdisplay) { + + $did_title = ($did['description'] != '') ? $did['description'] : _("DID / CID"); + + $addURL = $_SERVER['PHP_SELF'].'?type=setup&display=did&&extdisplay='.$did['extension'].'/'.$did['cidnum']; + $did_icon = 'images/email_edit.png'; + $did_label = trim($did['extension']) == '' ? ' Any DID' : ' '.$did['extension']; + if (trim($did['cidnum']) != '') { + $did_label .= ' / '.$did['cidnum']; + } + if (trim($did['description']) != '') { + $did_label .= ' ('.$did['description'].')'; + } + + $did_label = '  + '.$did_label. + ' '; + + $currentcomponent->addguielem($section, new gui_link('did_'.$did_count++, $did_label, $addURL, true, false), 4); + } + } + + $section = _("Recording Options"); + $currentcomponent->addguielem($section, new gui_selectbox('record_in', $currentcomponent->getoptlist('recordoptions'), $record_in, _("Record Incoming"), _("Record all inbound calls received at this extension."), false)); + $currentcomponent->addguielem($section, new gui_selectbox('record_out', $currentcomponent->getoptlist('recordoptions'), $record_out, _("Record Outgoing"), _("Record all outbound calls received at this extension."), false)); + } +} + +function core_users_configprocess() { + if ( !class_exists('agi_asteriskmanager') ) + include 'common/php-asmanager.php'; + + //create vars from the request + extract($_REQUEST); + + //make sure we can connect to Asterisk Manager + if (!checkAstMan()) { + return false; + } + + //check if the extension is within range for this user + if (isset($extension) && !checkRange($extension)){ + echo ""; + $GLOBALS['abort'] = true; + } else { + //if submitting form, update database + if (!isset($action)) $action = null; + switch ($action) { + case "add": + $conflict_url = array(); + $usage_arr = framework_check_extension_usage($_REQUEST['extension']); + if (!empty($usage_arr)) { + $GLOBALS['abort'] = true; + $conflict_url = framework_display_extension_usage_alert($usage_arr,true); + global $currentcomponent; + $id=0; + $currentcomponent->addguielem('_top', new gui_link_label('conflict', _("Conflicting Extensions"), _("The following extension numbers are in conflict, you can click on the item(s) below to edit the conflicting entity."), true)); + foreach ($conflict_url as $edit_link) { + $currentcomponent->addguielem('_top', new gui_link('conflict'.$i++, $edit_link['label'], $edit_link['url'])); + } + $msg = ($_REQUEST['display'] == 'users') ? _("Configure user again:") : _("Configure extension again:"); + $currentcomponent->addguielem('_top', new gui_subheading('conflict_end', $msg, false)); + unset($_REQUEST['action']); + redirect_standard_continue(); + } elseif (core_users_add($_REQUEST)) { + needreload(); + redirect_standard_continue(); + } else { + // really bad hack - but if core_users_add fails, want to stop core_devices_add + // Comment, this does not help everywhere. Other hooks functions can hook before + // this like voicemail! + // + $GLOBALS['abort'] = true; + } + break; + case "del": + core_users_del($extdisplay); + core_users_cleanastdb($extdisplay); + if (function_exists('findmefollow_del')) { + findmefollow_del($extdisplay); + } + needreload(); + redirect_standard_continue(); + break; + case "edit": + if (core_users_edit($extdisplay,$_REQUEST)) { + needreload(); + redirect_standard_continue('extdisplay'); + } else { + // really bad hack - but if core_users_edit fails, want to stop core_devices_edit + $GLOBALS['abort'] = true; + } + break; + } + } + return true; +} + + +function core_devices_configpageinit($dispnum) { + global $currentcomponent; + + if ( $dispnum == 'devices' || $dispnum == 'extensions' ) { + // Setup arrays for device types + $currentcomponent->addgeneralarray('devtechs'); + + // Some errors for the validation bits + $msgInvalidDTMFMODE = _("Please enter the dtmfmode for this device"); + $msgInvalidChannel = _("Please enter the channel for this device"); + $msgConfirmSecret = _("You have not entered a Secret for this device, although this is possible it is generally bad practice to not assign a Secret to a device. Are you sure you want to leave the Secret empty?"); + $msgInvalidSecret = _("Please enter a Secret for this device"); + + // zap + $tmparr = array(); + $tmparr['channel'] = array('value' => '', 'level' => 0, 'jsvalidation' => 'isEmpty()', 'failvalidationmsg' => $msgInvalidChannel); + $tmparr['context'] = array('value' => 'from-internal', 'level' => 1); + $tmparr['immediate'] = array('value' => 'no', 'level' => 1); + $tmparr['signalling'] = array('value' => 'fxo_ks', 'level' => 1); + $tmparr['echocancel'] = array('value' => 'yes', 'level' => 1); + $tmparr['echocancelwhenbridged'] = array('value' => 'no', 'level' => 1); + $tmparr['echotraining'] = array('value' => '800', 'level' => 1); + $tmparr['busydetect'] = array('value' => 'no', 'level' => 1); + $tmparr['busycount'] = array('value' => '7', 'level' => 1); + $tmparr['callprogress'] = array('value' => 'no', 'level' => 1); + $tmparr['dial'] = array('value' => '', 'level' => 1); + $tmparr['accountcode'] = array('value' => '', 'level' => 1); + $tmparr['callgroup'] = array('value' => '', 'level' => 1); + $tmparr['pickupgroup'] = array('value' => '', 'level' => 1); + $tmparr['mailbox'] = array('value' => '', 'level' => 1); + $currentcomponent->addgeneralarrayitem('devtechs', 'zap', $tmparr); + unset($tmparr); + + // iax2 + $tmparr = array(); + $tmparr['secret'] = array('value' => '', 'level' => 0, 'jsvalidation' => 'isEmpty() && !confirm("'.$msgConfirmSecret.'")', 'failvalidationmsg' => $msgInvalidSecret); + $tmparr['notransfer'] = array('value' => 'yes', 'level' => 1); + $tmparr['context'] = array('value' => 'from-internal', 'level' => 1); + $tmparr['host'] = array('value' => 'dynamic', 'level' => 1); + $tmparr['type'] = array('value' => 'friend', 'level' => 1); + $tmparr['port'] = array('value' => '4569', 'level' => 1); + $tmparr['qualify'] = array('value' => 'yes', 'level' => 1); + $tmparr['disallow'] = array('value' => '', 'level' => 1); + $tmparr['allow'] = array('value' => '', 'level' => 1); + $tmparr['dial'] = array('value' => '', 'level' => 1); + $tmparr['accountcode'] = array('value' => '', 'level' => 1); + $tmparr['mailbox'] = array('value' => '', 'level' => 1); + $currentcomponent->addgeneralarrayitem('devtechs', 'iax2', $tmparr); + unset($tmparr); + + // sip + $tmparr = array(); + $tmparr['secret'] = array('value' => '', 'level' => 0, 'jsvalidation' => 'isEmpty() && !confirm("'.$msgConfirmSecret.'")', 'failvalidationmsg' => $msgInvalidSecret); + $tmparr['dtmfmode'] = array('value' => 'rfc2833', 'level' => 0, 'jsvalidation' => 'isEmpty()', 'failvalidationmsg' => $msgInvalidDTMFMODE ); + $tmparr['canreinvite'] = array('value' => 'no', 'level' => 1); + $tmparr['context'] = array('value' => 'from-internal', 'level' => 1); + $tmparr['host'] = array('value' => 'dynamic', 'level' => 1); + $tmparr['type'] = array('value' => 'friend', 'level' => 1); + $tmparr['nat'] = array('value' => 'yes', 'level' => 1); + $tmparr['port'] = array('value' => '5060', 'level' => 1); + $tmparr['qualify'] = array('value' => 'yes', 'level' => 1); + $tmparr['callgroup'] = array('value' => '', 'level' => 1); + $tmparr['pickupgroup'] = array('value' => '', 'level' => 1); + $tmparr['disallow'] = array('value' => '', 'level' => 1); + $tmparr['allow'] = array('value' => '', 'level' => 1); + $tmparr['dial'] = array('value' => '', 'level' => 1); + $tmparr['accountcode'] = array('value' => '', 'level' => 1); + $tmparr['mailbox'] = array('value' => '', 'level' => 1); + $currentcomponent->addgeneralarrayitem('devtechs', 'sip', $tmparr); + unset($tmparr); + + // custom + $tmparr = array(); + $tmparr['dial'] = array('value' => '', 'level' => 0); + $currentcomponent->addgeneralarrayitem('devtechs', 'custom', $tmparr); + unset($tmparr); + + // Devices list + $currentcomponent->addoptlistitem('devicelist', 'sip_generic', _("Generic SIP Device")); + $currentcomponent->addoptlistitem('devicelist', 'iax2_generic', _("Generic IAX2 Device")); + $currentcomponent->addoptlistitem('devicelist', 'zap_generic', _("Generic ZAP Device")); + $currentcomponent->addoptlistitem('devicelist', 'custom_custom', _("Other (Custom) Device")); + $currentcomponent->setoptlistopts('devicelist', 'sort', false); + + + // Option lists used by the gui + $currentcomponent->addoptlistitem('devicetypelist', 'fixed', _("Fixed")); + $currentcomponent->addoptlistitem('devicetypelist', 'adhoc', _("Adhoc")); + $currentcomponent->setoptlistopts('devicetypelist', 'sort', false); + + $currentcomponent->addoptlistitem('deviceuserlist', 'none', _("none")); + $currentcomponent->addoptlistitem('deviceuserlist', 'new', _("New User")); + $users = core_users_list(); + if (isset($users)) { + foreach ($users as $auser) { + $currentcomponent->addoptlistitem('deviceuserlist', $auser[0], $auser[0]); + } + } + $currentcomponent->setoptlistopts('deviceuserlist', 'sort', false); + + // Add the 'proces' functions + $currentcomponent->addguifunc('core_devices_configpageload'); + $currentcomponent->addprocessfunc('core_devices_configprocess'); + } +} + +function core_devices_configpageload() { + global $currentcomponent; + + // Init vars from $_REQUEST[] + $display = isset($_REQUEST['display'])?$_REQUEST['display']:null;; + $action = isset($_REQUEST['action'])?$_REQUEST['action']:null; + $extdisplay = isset($_REQUEST['extdisplay'])?$_REQUEST['extdisplay']:null; + $tech_hardware = isset($_REQUEST['tech_hardware'])?$_REQUEST['tech_hardware']:null; + + if ( $action == 'del' ) { // Deleted + + if ( $display != 'extensions' ) + $currentcomponent->addguielem('_top', new gui_subheading('del', $extdisplay.' '._("deleted"), false)); + + } elseif ( $extdisplay == '' && $tech_hardware == '' ) { // Adding + + if ( $display != 'extensions') { + $currentcomponent->addguielem('_top', new gui_pageheading('title', _("Add Device")), 0); + } else { + $currentcomponent->addguielem('_top', new gui_pageheading('title', _("Add an Extension")), 0); + } + $currentcomponent->addguielem('_top', new gui_label('instructions', _("Please select your Device below then click Submit"))); + $currentcomponent->addguielem('Device', new gui_selectbox('tech_hardware', $currentcomponent->getoptlist('devicelist'), '', _("Device"), '', false)); + + } else { + + $deviceInfo = array(); + if ( $extdisplay ) { // Editing + + $deviceInfo = core_devices_get($extdisplay); + + if ( $display != 'extensions' ) { + $currentcomponent->addguielem('_top', new gui_pageheading('title', _("Device").": $extdisplay", false), 0); + + $delURL = $_SERVER['PHP_SELF'].'?'.$_SERVER['QUERY_STRING'].'&action=del'; + $tlabel = sprintf(_("Delete Device %s"),$extdisplay); + $label = ' '.$tlabel.''; + $currentcomponent->addguielem('_top', new gui_link('del', $label, $delURL, true, false), 0); + + if ($deviceInfo['device_user'] != 'none') { + $editURL = $_SERVER['PHP_SELF'].'?type=setup&display=users&skip=0&extdisplay='.$deviceInfo['user']; + $tlabel = $deviceInfo['devicetype'] == 'adhoc' ? sprintf(_('Edit Default User: %s'),$deviceInfo['user']) : sprintf(_('Edit Fixed User: %s'),$deviceInfo['user']); + $label = ' '.$tlabel.''; + $currentcomponent->addguielem('_top', new gui_link('edit_user', $label, $editURL, true, false), 0); + } + } + } else { + + $tmparr = explode('_', $tech_hardware); + $deviceInfo['tech'] = $tmparr[0]; + $deviceInfo['hardware'] = $tmparr[1]; + unset($tmparr); + + if ( $display != 'extensions' ) { + $currentcomponent->addguielem('_top', new gui_pageheading('title', _("Add").' '.strtoupper($deviceInfo['tech']).' '._("Device")), 0); + } else { + $currentcomponent->addguielem('_top', new gui_pageheading('title', _("Add").' '.strtoupper($deviceInfo['tech']).' '._("Extension")), 0); + } + + } + + // Ensure they exist before the extract + $devinfo_description = $devinfo_emergency_cid = null; + $devinfo_devicetype = $devinfo_user = $devinfo_hardware = null; + if ( is_array($deviceInfo) ) { + extract($deviceInfo, EXTR_PREFIX_ALL, 'devinfo'); + } + + // Setup vars for use in the gui later on + $fc_logon = featurecodes_getFeatureCode('core', 'userlogon'); + $fc_logoff = featurecodes_getFeatureCode('core', 'userlogoff'); + + $msgInvalidDevID = _("Please enter a device id."); + $msgInvalidDevDesc = _("Please enter a valid Description for this device"); + $msgInvalidEmergCID = _("Please enter a valid Emergency CID"); + $msgInvalidExtNum = _("Please enter a valid extension number."); + + // Actual gui + $currentcomponent->addguielem('_top', new gui_hidden('action', ($extdisplay ? 'edit' : 'add'))); + $currentcomponent->addguielem('_top', new gui_hidden('extdisplay', $extdisplay)); + + if ( $display != 'extensions' ) { + $section = _("Device Info"); + if ( $extdisplay ) { // Editing + $currentcomponent->addguielem($section, new gui_hidden('deviceid', $extdisplay)); + } else { // Adding + $currentcomponent->addguielem($section, new gui_textbox('deviceid', $extdisplay, _("Device ID"), _("Give your device a unique integer ID. The device will use this ID to authenticate to the system."), '!isInteger()', $msgInvalidDevID, false)); + } + $currentcomponent->addguielem($section, new gui_textbox('description', $devinfo_description, _("Description"), _("The caller id name for this device will be set to this description until it is logged into."), '!isAlphanumeric() || isWhitespace()', $msgInvalidDevDesc, false)); + $currentcomponent->addguielem($section, new gui_textbox('emergency_cid', $devinfo_emergency_cid, _("Emergency CID"), _("This caller id will always be set when dialing out an Outbound Route flagged as Emergency. The Emergency CID overrides all other caller id settings."), '!isCallerID()', $msgInvalidEmergCID)); + $currentcomponent->addguielem($section, new gui_selectbox('devicetype', $currentcomponent->getoptlist('devicetypelist'), $devinfo_devicetype, _("Device Type"), _("Devices can be fixed or adhoc. Fixed devices are always associated to the same extension/user. Adhoc devices can be logged into and logged out of by users.").' '.$fc_logon.' '._("logs into a device.").' '.$fc_logoff.' '._("logs out of a device."), false)); + $currentcomponent->addguielem($section, new gui_selectbox('deviceuser', $currentcomponent->getoptlist('deviceuserlist'), $devinfo_user, _("Default User"), _("Fixed devices will always mapped to this user. Adhoc devices will be mapped to this user by default.

If selecting 'New User', a new User Extension of the same Device ID will be set as the Default User."), false)); + } else { + $section = _("Extension Options"); + $currentcomponent->addguielem($section, new gui_textbox('emergency_cid', $devinfo_emergency_cid, _("Emergency CID"), _("This caller id will always be set when dialing out an Outbound Route flagged as Emergency. The Emergency CID overrides all other caller id settings."), '!isCallerID()', $msgInvalidEmergCID)); + } + $currentcomponent->addguielem($section, new gui_hidden('tech', $devinfo_tech)); + $currentcomponent->addguielem($section, new gui_hidden('hardware', $devinfo_hardware)); + + $section = _("Device Options"); + $currentcomponent->addguielem($section, new gui_label('techlabel', sprintf(_("This device uses %s technology."),$devinfo_tech)),4); + $devopts = $currentcomponent->getgeneralarrayitem('devtechs', $devinfo_tech); + if (is_array($devopts)) { + foreach ($devopts as $devopt=>$devoptarr) { + $devopname = 'devinfo_'.$devopt; + $devoptcurrent = isset($$devopname) ? $$devopname : $devoptarr['value']; + $devoptjs = isset($devoptarr['jsvalidation']) ? $devoptarr['jsvalidation'] : ''; + $devoptfailmsg = isset($devoptarr['failvalidationmsg']) ? $devoptarr['failvalidationmsg'] : ''; + + if ( $devoptarr['level'] == 0 || ($extdisplay && $devoptarr['level'] == 1) ) { // editing to show advanced as well + $currentcomponent->addguielem($section, new gui_textbox($devopname, $devoptcurrent, $devopt, '', $devoptjs, $devoptfailmsg), 4); + } else { // add so only basic + $currentcomponent->addguielem($section, new gui_hidden($devopname, $devoptcurrent), 4); + } + } + } + } +} + +function core_devices_configprocess() { + if ( !class_exists('agi_asteriskmanager') ) + include 'common/php-asmanager.php'; + + //make sure we can connect to Asterisk Manager + if (!checkAstMan()) { + return false; + } + + //create vars from the request + extract($_REQUEST); + + $extension = isset($extension)?$extension:null; + $deviceid = isset($deviceid)?$deviceid:null; + $name = isset($name)?$name:null; + $action = isset($action)?$action:null; + + // fixed users only in extensions mode + if ( $display == 'extensions' ) { + $devicetype = 'fixed'; + $deviceid = $deviceuser = $extension; + $description = $name; + } + + //if submitting form, update database + switch ($action) { + case "add": + // really bad hack - but if core_users_add fails, want to stop core_devices_add + if (!isset($GLOBALS['abort']) || $GLOBALS['abort'] !== true) { + if (core_devices_add($deviceid,$tech,$devinfo_dial,$devicetype,$deviceuser,$description,$emergency_cid)) { + needreload(); + if ($deviceuser != 'new') { + redirect_standard_continue(); + } + } + } + break; + case "del": + core_devices_del($extdisplay); + needreload(); + redirect_standard_continue(); + break; + case "edit": //just delete and re-add + // really bad hack - but if core_users_edit fails, want to stop core_devices_edit + if (!isset($GLOBALS['abort']) || $GLOBALS['abort'] !== true) { + core_devices_del($extdisplay,true); + core_devices_add($deviceid,$tech,$devinfo_dial,$devicetype,$deviceuser,$description,$emergency_cid,true); + needreload(); + redirect_standard_continue('extdisplay'); + } + break; + case "resetall": //form a url with this option to nuke the AMPUSER & DEVICE trees and start over. + core_users2astdb(); + core_devices2astdb(); + break; + } + return true; +} + +?> Index: /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/bin/fax-process.pl =================================================================== --- /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/bin/fax-process.pl (revision 6236) +++ /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/bin/fax-process.pl (revision 6236) @@ -0,0 +1,225 @@ +#!/usr/bin/perl -w + +# Small program to process a tiff file into a PDF and email it. +# +# Distributed under the terms of the GNU General Public License (GPL) Version 2 +# Copyright 2005 by Rob Thomas (xrobau@gmail.com) + +use MIME::Base64; +use Net::SMTP; + +# Default paramaters +my $to = "xrobau\@gmail.com"; +my $from = "fax\@"; +my $dest = undef; +my $subject = "Fax received"; +my $ct = "application/x-pdf"; +my $file = undef; +my $attachment = undef; + +# Care about the hostname. +my $hostname = `/bin/hostname`; +chomp ($hostname); +if ($hostname =~ /localhost/) { + $hostname = "set.your.hostname.com"; +} +$from .= $hostname; + +# Usage: +my $usage="Usage: --file filename [--attachment filename] [--to email_address] [--from email_address] [--type content/type] [--subject \"Subject Of Email\"] [--dest DID]"; + +# Parse command line.. +while (my $cmd = shift @ARGV) { + chomp $cmd; + # My kingdom for a 'switch' + if ($cmd eq "--to") { + my $tmp = shift @ARGV; + $to = $tmp if (defined $tmp); + } elsif ($cmd eq "--subject") { + my $tmp = shift @ARGV; + if ($tmp =~ /\^(\")|^(\')/) { + # It's a quoted string + my $delim = $+; # $+ is 'last match', which is ' or " + $tmp =~ s/\Q$delim\E//; # Strip out ' or " + $subject = $tmp; + while ($tmp = shift @ARGV) { + if ($tmp =~ /\Q$delim\E/) { + $tmp =~ s/\Q$delim\E//; + last; + } + $subject .= $tmp; + } + } else { + # It's a single word + $subject = $tmp; + } + # Convert %2x to proper characters, leave anything else alone. + $subject =~ s/\%20/ /g; + $subject =~ s/\%21/\!/g; + $subject =~ s/\%22/\"/g; + $subject =~ s/\%23/\#/g; + $subject =~ s/\%24/\$/g; + $subject =~ s/\%25/\%/g; + $subject =~ s/\%26/\&/g; + $subject =~ s/\%27/\'/g; + $subject =~ s/\%28/\(/g; + $subject =~ s/\%29/\)/g; + $subject =~ s/\%2a/\*/g; + $subject =~ s/\%2A/\*/g; + $subject =~ s/\%2b/\+/g; + $subject =~ s/\%2B/\+/g; + $subject =~ s/\%2c/\,/g; + $subject =~ s/\%2C/\,/g; + $subject =~ s/\%2d/\-/g; + $subject =~ s/\%2D/\-/g; + $subject =~ s/\%2e/\./g; + $subject =~ s/\%2E/\./g; + $subject =~ s/\%2f/\//g; + $subject =~ s/\%2F/\//g; + } elsif ($cmd eq "--type") { + my $tmp = shift @ARGV; + $ct = $tmp if (defined $tmp); + } elsif ($cmd eq "--from") { + my $tmp = shift @ARGV; + $from = $tmp if (defined $tmp); + } elsif ($cmd eq "--file") { + my $tmp = shift @ARGV; + $file = $tmp if (defined $tmp); + } elsif ($cmd eq "--attachment") { + my $tmp = shift @ARGV; + $attachment = $tmp if (defined $tmp); + } elsif ($cmd eq "--dest") { + my $tmp = shift @ARGV; + if ($tmp =~ /\^(\")|^(\')/) { + # It's a quoted string + my $delim = $+; # $+ is 'last match', which is ' or " + $tmp =~ s/\Q$delim\E//; # Strip out ' or " + $dest = $tmp; + while ($tmp = shift @ARGV) { + if ($tmp =~ /\Q$delim\E/) { + $tmp =~ s/\Q$delim\E//; + last; + } + $dest .= $tmp; + } + } else { + # It's a single word + $dest = $tmp; + } + } else { + die "$cmd not understood\n$usage\n"; + } + +} + +# OK. All our variables are set up. +# Lets make sure that we know about a file... +die $usage unless $file; +# and that the file exists... +open( FILE, $file ) or die "Error opening $file: $!"; +# Oh, did we possibly not specify an attachment name? +$attachment = $file unless ($attachment); + +my $encoded=""; +my $enc_gif=""; +my $buf=""; +my $convert_status=0; + +# First, lets find out if it's a TIFF file +read(FILE, $buf, 4); +if ($buf eq "MM\x00\x2a" || $buf eq "II\x2a\x00") { + # Tiff magic - We need to convert it to pdf first + # Need to do some error testing here - what happens if tiff2pdf + # doesn't exist? + open PDF, "tiff2pdf $file|"; + $buf = ""; + while (read(PDF, $buf, 60*57)) { + $encoded .= encode_base64($buf); + } + close PDF; + + open GIF, "convert -resize '50%' -monochrome -delay 300 ${file}[0,1] gif:- |"; + if (!eof(GIF)) { + $convert_status=1; + $buf = ""; + while (read(GIF, $buf, 60*57)) { + $enc_gif .= encode_base64($buf); + } + } + close GIF; +} else { + # It's a PDF already + # Go back to the start of the file, and start again + seek(FILE, 0, 0); + while (read(FILE, $buf, 60*57)) { + $encoded .= encode_base64($buf); + } +} +close FILE; + +# Now we have the file, we should ensure that there's no paths on the +# filename.. +$attachment =~ s/^.+\///; + +# And that's pretty much all the hard work done. Now we just create the +# headers for the MIME encapsulation: +my $boundary = '------FREEPBX_FAX_MAIL:'; +my $dtime = `date -R`; +chomp $dtime; +my @chrs = ('0' .. '9', 'A' .. 'Z', 'a' .. 'z'); +foreach (0..16) { $boundary .= $chrs[rand (scalar @chrs)]; } + +my $len = length $encoded; +my $len_gif = length $enc_gif; +# message body.. +my $msg ="Content-Class: urn:content-classes:message +Content-Transfer-Encoding: 7bit +MIME-Version: 1.0 +Content-Type: multipart/mixed; boundary=\"$boundary\" +From: $from +Date: $dtime +Reply-To: $from +X-Mailer: dofaxmail.pl +To: $to +Subject: $subject + +This is a multi-part message in MIME format. + +--$boundary +Content-Type: text/plain; charset=\"us-ascii\" +Content-Transfer-Encoding: quoted-printable + +A Fax has been received by the fax gateway and is attached to this message. + +The destination number for this fax is ".$dest." + + +"; +if ($convert_status eq 1) { +$msg=$msg."--$boundary +Content-Type: image/gif; name=\"thumb-".substr($attachment,0,-4).".gif\" +Content-Transfer-Encoding: base64 + +$enc_gif +"; +} + +$msg=$msg."--$boundary +Content-Type: $ct; name=\"$attachment\" +Content-Transfer-Encoding: base64 +Content-Disposition: attachment; filename=\"$attachment\" + +$encoded +--$boundary-- +"; + +#print "$msg"; +# Now we just send it. +my $smtp = Net::SMTP-> new("127.0.0.1", Debug => 0) or + die "Net::SMTP::new: $!"; +$smtp-> mail($from); +$smtp-> recipient($to); +$smtp-> data(); +$smtp-> datasend($msg); +$smtp-> dataend(); + Index: /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/etc/sip.conf =================================================================== --- /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/etc/sip.conf (revision 5450) +++ /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/etc/sip.conf (revision 5450) @@ -0,0 +1,25 @@ +; do not edit this file, this is an auto-generated file by freepbx +; all modifications must be done from the web gui + +[general] +; +; enable and force the sip jitterbuffer. If these settings are desired +; they should be set in the sip_general_custom.conf file as this file +; will get overwritten during reloads and upgrades. +; +; jbenable=yes +; jbforce=yes + +; These will all be included in the [general] context +; +#include sip_general_additional.conf +#include sip_general_custom.conf +#include sip_nat.conf +#include sip_registrations_custom.conf +#include sip_registrations.conf + +; These should all be expected to come after the [general] context +; +#include sip_custom.conf +#include sip_additional.conf +#include sip_custom_post.conf Index: /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/etc/features.conf =================================================================== --- /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/etc/features.conf (revision 5527) +++ /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/etc/features.conf (revision 5527) @@ -0,0 +1,15 @@ +; +; DO NOT EDIT THIS FILE, USE THE _CUSTOM INCLUDE FILES BELOW FOR CHANGES +; + +[general] +#include features_general_additional.conf +#include features_general_custom.conf + +[applicationmap] +#include features_applicationmap_additional.conf +#include features_applicationmap_custom.conf + +[featuremap] +#include features_featuremap_additional.conf +#include features_featuremap_custom.conf Index: /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/etc/extensions.conf =================================================================== --- /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/etc/extensions.conf (revision 6594) +++ /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/etc/extensions.conf (revision 6594) @@ -0,0 +1,1094 @@ +; do not edit this file, this is an auto-generated file by freepbx +; all modifications must be done from the web gui + +; FreePBX +; Copyright (C) 2004 Coalescent Systems Inc (Canada) +; Copyright (C) 2006 Why Pay More 4 Less Pty Ltd (Australia) +; Copyright (C) 2007 Astrogen LLC (USA) +; Released under the GNU GPL Licence version 2. + +; dialparties.agi (http://www.sprackett.com/asterisk/) +; Asterisk::AGI (http://asterisk.gnuinter.net/) +; gsm (http://www.ibiblio.org/pub/Linux/utils/compress/!INDEX.short.html) +; loligo sounds (http://www.loligo.com/asterisk/sounds/) +; mpg123 (http://voip-info.org/wiki-Asterisk+config+musiconhold.conf) + +;************************** -WARNING- **************************************** +; * +; This include file is to be used with extreme caution. In almost all cases * +; any custom dialplan SHOULD be put in extensions_custom.conf which will * +; not hurt freepbx generated dialplan. In some very rare and custom situation * +; users have a need to override what freepbx generates. Anything in this file * +; will do such. * +; * +#include extensions_override_freepbx.conf +; * +;************************** -WARNING- **************************************** + +; include extension contexts generated from AMP +#include extensions_additional.conf + +; Customizations to this dialplan should be made in extensions_custom.conf +; See extensions_custom.conf.sample for an example +#include extensions_custom.conf + +[from-trunk] ; just an alias since VoIP shouldn't be called PSTN +include => from-pstn + +[from-pstn] +include => from-pstn-custom ; create this context in extensions_custom.conf to include customizations +include => ext-did +include => from-did-direct ; MODIFICATOIN (PL) for findmefollow if enabled, should be bofore ext-local +include => ext-did-catchall ; THIS MUST COME AFTER ext-did +exten => fax,1,Goto(ext-fax,in_fax,1) + +; MODIFICATION (PL) +; +; Required to assure that direct dids go to personal ring group before local extension. +; This could be auto-generated however I it is prefered to be put here and hard coded +; so that it can be modified if ext-local should take precedence in certain situations. +; will have to decide what to do later. +; +[from-did-direct] +include => ext-findmefollow +include => ext-local + + + +; ############################################################################ +; Macros [macro] +; ############################################################################ + +; Rings one or more extensions. Handles things like call forwarding and DND +; We don't call dial directly for anything internal anymore. +; ARGS: $TIMER, $OPTIONS, $EXT1, $EXT2, $EXT3, ... +; Use a Macro call such as the following: +; Macro(dial,$DIAL_TIMER,$DIAL_OPTIONS,$EXT1,$EXT2,$EXT3,...) +[macro-dial] +exten => s,1,GotoIf($["${MOHCLASS}" = ""]?dial) +exten => s,n,SetMusicOnHold(${MOHCLASS}) +exten => s,n(dial),AGI(dialparties.agi) +exten => s,n,NoOp(Returned from dialparties with no extensions to call and DIALSTATUS: ${DIALSTATUS}) + +exten => s,n+2(normdial),Dial(${ds}) ; dialparties will set the priority to 10 if $ds is not null +exten => s,n,Set(DIALSTATUS=${IF($["${DIALSTATUS_CW}"!="" ]?${DIALSTATUS_CW}:${DIALSTATUS})}) +exten => s,n,GosubIf($["${SCREEN}" != ""]?${DIALSTATUS},1) + +exten => s,20(huntdial),NoOp(Returned from dialparties with hunt groups to dial ) +exten => s,n,Set(HuntLoop=0) +exten => s,n(a22),GotoIf($[${HuntMembers} >= 1]?a30) ; if this is from rg-group, don't strip prefix +exten => s,n,NoOp(Returning there are no members left in the hunt group to ring) + +; dialparties.agi has setup the dialstring for each hunt member in a variable labeled HuntMember0, HuntMember1 etc for each iteration +; and The total number in HuntMembers. So for each iteration, we will update the CALLTRACE Data. +; +exten => s,n+2(a30),Set(HuntMember=HuntMember${HuntLoop}) +exten => s,n,GotoIf($[$["${CALLTRACE_HUNT}" != "" ] & $["${RingGroupMethod}" = "hunt" ]]?a32:a35) +exten => s,n(a32),Set(CT_EXTEN=${CUT(FILTERED_DIAL,,$[${HuntLoop} + 1])}) +exten => s,n,Set(DB(CALLTRACE/${CT_EXTEN})=${CALLTRACE_HUNT}) +exten => s,n,Goto(s,a42) + +;Set Call Trace for each hunt member we are going to call "Memory groups have multiple members to set CALL TRACE For hence the loop +; +exten => s,n(a35),GotoIf($[$["${CALLTRACE_HUNT}" != "" ] & $["${RingGroupMethod}" = "memoryhunt" ]]?a36:a50) +exten => s,n(a36),Set(CTLoop=0) +exten => s,n(a37),GotoIf($[${CTLoop} > ${HuntLoop}]?a42) ; if this is from rg-group, don't strip prefix +exten => s,n,Set(CT_EXTEN=${CUT(FILTERED_DIAL,,$[${CTLoop} + 1])}) +exten => s,n,Set(DB(CALLTRACE/${CT_EXTEN})=${CALLTRACE_HUNT}) +exten => s,n,Set(CTLoop=$[1 + ${CTLoop}]) +exten => s,n,Goto(s,a37) + +exten => s,n(a42),Dial(${${HuntMember}}${ds}) +exten => s,n,Set(HuntLoop=$[1 + ${HuntLoop}]) +exten => s,n,GotoIf($[$[$["foo${RingGroupMethod}" != "foofirstavailable"] & $["foo${RingGroupMethod}" != "foofirstnotonphone"]] | $["foo${DialStatus}" = "fooBUSY"]]?a46) +exten => s,n,Set(HuntMembers=0) +exten => s,n(a46),Set(HuntMembers=$[${HuntMembers} - 1]) +exten => s,n,Goto(s,a22) + +exten => s,n(a50),DBdel(CALLTRACE/${CT_EXTEN}) +exten => s,n,Goto(s,a42) + +; For call screening +exten => NOANSWER,1,Macro(vm,${SCREEN_EXTEN},BUSY,${IVR_RETVM}) +exten => NOANSWER,n,GotoIf($["${IVR_RETVM}" != "RETURN" | "${IVR_CONTEXT}" = ""]?bye) +exten => NOANSWER,n,Return +exten => NOANSWER,n(bye),Macro(hangupcall) +exten => TORTURE,1,Goto(app-blackhole,musiconhold,1) +exten => TORTURE,n,Macro(hangupcall) +exten => DONTCALL,1,Answer +exten => DONTCALL,n,Wait(1) +exten => DONTCALL,n,Zapateller() +exten => DONTCALL,n,Playback(ss-noservice) +exten => DONTCALL,n,Macro(hangupcall) + +; make sure hungup calls go here so that proper cleanup occurs from call confirmed calls and the like +; +exten => h,1,Macro(hangupcall) + +; Ring an extension, if the extension is busy or there is no answer send it +; to voicemail +; ARGS: $VMBOX, $EXT +[macro-exten-vm] +exten => s,1,Macro(user-callerid) + +exten => s,n,Set(RingGroupMethod=none) +exten => s,n,Set(VMBOX=${ARG1}) +exten => s,n,Set(EXTTOCALL=${ARG2}) +exten => s,n,Set(CFUEXT=${DB(CFU/${EXTTOCALL})}) +exten => s,n,Set(CFBEXT=${DB(CFB/${EXTTOCALL})}) +exten => s,n,Set(RT=${IF($[$["${VMBOX}"!="novm"] | $["foo${CFUEXT}"!="foo"]]?${RINGTIMER}:"")}) +exten => s,n,Macro(record-enable,${EXTTOCALL},IN) +exten => s,n,Macro(dial,${RT},${DIAL_OPTIONS},${EXTTOCALL}) +exten => s,n,GotoIf($[ $["${VMBOX}" != "novm"] & $["${SCREEN}" != ""] & $["${DIALSTATUS}" = "NOANSWER"] ]?exit,return) +exten => s,n,Set(SV_DIALSTATUS=${DIALSTATUS}) +exten => s,n,GosubIf($[$["${SV_DIALSTATUS}"="NOANSWER"] & $["${CFUEXT}"!=""] & $["${SCREEN}" = ""]?docfu,1) ; check for CFU in use on no answer +exten => s,n,GosubIf($[$["${SV_DIALSTATUS}"="BUSY"] & $["${CFBEXT}"!=""]]?docfb,1) ; check for CFB in use on busy +exten => s,n,Set(DIALSTATUS=${SV_DIALSTATUS}) +exten => s,n,NoOp(Voicemail is '${VMBOX}') +exten => s,n,GotoIf($["${VMBOX}" = "novm"]?s-${DIALSTATUS},1) ; no voicemail in use for this extension +exten => s,n,NoOp(Sending to Voicemail box ${EXTTOCALL}) +exten => s,n,Macro(vm,${VMBOX},${DIALSTATUS},${IVR_RETVM}) + +; Try the Call Forward on No Answer / Unavailable number +exten => docfu,1,Set(RTCFU=${IF($["${VMBOX}"!="novm"]?${RINGTIMER}:"")}) +exten => docfu,n,Dial(Local/${CFUEXT}@from-internal/n,${RTCFU},${DIAL_OPTIONS}) +exten => docfu,n,Return + +; Try the Call Forward on Busy number +exten => docfb,1,Set(RTCFB=${IF($["${VMBOX}"!="novm"]?${RINGTIMER}:"")}) +exten => docfb,n,Dial(Local/${CFBEXT}@from-internal/n,${RTCFB},${DIAL_OPTIONS}) +exten => docfb,n,Return + +; Extensions with no Voicemail box reporting BUSY come here +exten => s-BUSY,1,NoOp(Extension is reporting BUSY and not passing to Voicemail) +exten => s-BUSY,n,GotoIf($["${IVR_RETVM}" = "RETURN" & "${IVR_CONTEXT}" != ""]?exit,1) +exten => s-BUSY,n,Playtones(busy) +exten => s-BUSY,n,Busy(20) + +; Anything but BUSY comes here +exten => _s-.,1,Noop(IVR_RETVM: ${IVR_RETVM} IVR_CONTEXT: ${IVR_CONTEXT}) +exten => _s-.,n,GotoIf($["${IVR_RETVM}" = "RETURN" & "${IVR_CONTEXT}" != ""]?exit,1) +exten => _s-.,n,Playtones(congestion) +exten => _s-.,n,Congestion(10) + +; Short burst of tones then return +exten => exit,1,Playback(beep&line-busy-transfer-menu&silence/1) +exten => exit,n(return),MacroExit() + +;------------------------------------------------------------------------ +; [macro-vm] +;------------------------------------------------------------------------ +; CONTEXT: macro-vm +; PURPOSE: call voicemail system and extend with personal ivr +; +; Under normal use, this macro will call the voicemail system with the extension and +; desired greeting mode of busy, unavailable or as specified with direct voicemail +; calls (usually unavailable) when entered from destinations. +; +; The voicemail system's two greetings have been 'hijacked' as follows to extend the +; system by giving the option of a private 'ivr' for each voicemail user. The following +; applies to both the busy and unavailable modes of voicemail and can be applied to one +; or both, and differently. +; +; Global Defaults: +; +; The following are default values, used in both busy and unavail modes if no specific +; values are specified. +; +; VMX_REPEAT +; The number of times to repeat the users message if no option is pressed. +; VMX_TIMEOUT +; The timeout to wait after playing message before repeating or giving up. +; VMX_LOOPS +; The number of times it should replay the message and check for an option when +; an invalid option is pressed. +; +; VMX_OPTS_DOVM +; Default voicemail option to use if vm is chosen as an option. No options will +; cause Allison's generic message, 's' will go straight to beep. +; VMX_OPTS_TIMEOUT +; Default voicemail option to use if it times out with no options. No options will +; cause Allison's generic message, 's' will go straight to beep. +; IF THE USER PRESSES # - it will look like a timeout as well since no option will +; be presented. If the user wishes to enable a mode where a caller can press # +; during their message and it goes straight to voicemail with only a 'beep' then +; this should be set to 's'. +; VMX_OPTS_LOOPS +; Default voicemail option to use if to many wrong options occur. No options will +; cause Allison's generic message, 's' will go straight to beep. +; +; VMX_CONTEXT +; Default context for user destinations if not supplied in the user's settings +; VMX_PRI +; Default priority for user destinations if not supplied in the user's settings +; +; VMX_TIMEDEST_CONTEXT +; Default context for timeout destination if not supplied in the user's settings +; VMX_TIMEDEST_EXT +; Default extension for timeout destination if not supplied in the user's settings +; VMX_TIMEDEST_PRI +; Default priority for timeout destination if not supplied in the user's settings +; +; VMX_LOOPDEST_CONTEXT +; Default context for loops destination if not supplied in the user's settings +; VMX_LOOPDEST_EXT +; Default extension for loops destination if not supplied in the user's settings +; VMX_LOOPDEST_PRI +; Default priority for loops destination if not supplied in the user's settings +; +; +; The AMPUSER database variable has been extended with a 'vmx' tree (vm-extension). A +; duplicate set is included for both unavail and busy. You could choose for to have an +; ivr when unavail is taken, but not with busy - or a different once with busy. +; The full list is below, each specific entry is futher described: +; +; state: Whether teh current mode is enabled or disabled. Anything but 'enabled' is +; treated as disabled. +; repeat: This is the number of times that the users message should be played after the +; timeout if the user has not entered anything. It is just a variable to the +; Read() function which will do the repeating. +; timeout: This is how long to wait after the message has been read for a response from +; the user. A caller can enter a digit any time during the playback. +; loops: This is the number of loops that the system will allow a caller to retry if +; they enter a bad menu choice, before going to the loop failover destination +; vmxopts: This is the vm options to send to the voicemail command used when a specific +; voicemail destination is chosen (inidcated by 'dovm' in the ext field). This is +; typically either set to 's' or left blank. When set to 's' there will be no +; message played when entering the voicemail, just a beep. When blank, you will +; have Allison's generic message played. It is not typical to play the greetings +; since they have been 'hijacked' for these IVR's and from a caller's perspecitive +; this system appears interconnected with the voicemail so instructions can be +; left there. +; timedest: The three variables: ext, context and pri are the goto destination if the caller +; enters no options and it timesout. None have to be set and a system default +; will be used. If just ext is set, then defaults will be used for context and +; pri, etc. +; loopdest: This is identical to timedest but used if the caller exceeds the maximum invalid +; menu choices. +; [0-9*]: The user can specify up to 11 ivr options, all as single digits from 0-9 or *. The +; # key can not be used since it is used as a terminator key for the Read command +; and will never be returned. A minimum of the ext must be specified for each valid +; option and as above, the context and priority can also be specified if the default +; is not to be used. +; Option '0' takes on a special meaning. Since a user is able to break out of the +; voicemail command once entering it with a 0, if specified, the 0 destination will +; be used. +; Option '*' can also be used to breakout. It is undecided at this point whether +; providing that option will be used as well. (probably should). +; +; +; /AMPUSER//vmx/[busy|unavail]/state: enabled|disabled +; /AMPUSER//vmx/[busy|unavail]/repeat: n (times to repeat message) +; /AMPUSER//vmx/[busy|unavail]/timeout: n (timeout to wait for digit) +; /AMPUSER//vmx/[busy|unavail]/loops: n (loop returies for invalid entries) +; /AMPUSER//vmx/[busy|unavail]/vmxopts/dovm: vmoptions (if ext is dovm) +; /AMPUSER//vmx/[busy|unavail]/vmxopts/timeout: vmoptions (if timeout) +; /AMPUSER//vmx/[busy|unavail]/vmxopts/loops: vmoptions (if loops) +; /AMPUSER//vmx/[busy|unavail]/timedest/ext: extension (if timeout) +; /AMPUSER//vmx/[busy|unavail]/timedest/context: context (if timeout) +; /AMPUSER//vmx/[busy|unavail]/timedest/pri: priority (if timeout) +; /AMPUSER//vmx/[busy|unavail]/loopdest/ext: extension (if too many failures) +; /AMPUSER//vmx/[busy|unavail]/loopdest/context: context (if too many failures) +; /AMPUSER//vmx/[busy|unavail]/loopdest/pri: priority (if too many failures) +; /AMPUSER//vmx/[busy|unavail]/[0-9*]/ext: extension (dovm for vm access) +; /AMPUSER//vmx/[busy|unavail]/[0-9*]/context: context +; /AMPUSER//vmx/[busy|unavail]/[0-9*]/pri: priority +;------------------------------------------------------------------------ +[macro-vm] +; ARG1 - extension +; ARG2 - DIRECTDIAL/BUSY +; ARG3 - RETURN makes macro return, otherwise hangup +exten => s,1,Macro(user-callerid,SKIPTTL) +exten => s,n,Set(VMGAIN=${IF($["foo${VM_GAIN}"!="foo"]?"g(${VM_GAIN})":"")}) +; +; If BLKVM_OVERRIDE is set, then someone told us to block calls from going to +; voicemail. This variable is reset by the answering channel so subsequent +; transfers will properly function. +; +exten => s,n,GotoIf($["foo${DB(${BLKVM_OVERRIDE})}" != "fooTRUE"]?vmx,1) +; +; we didn't branch so block this from voicemail +; +exten => s,n,Noop(CAME FROM: ${NODEST} - Blocking VM cause of key: ${DB(BLKVM_OVERRIDE)}) + + +; If vmx not enabled for the current mode,then jump to normal voicemail behavior +; also - if not message (no-msg) is requested, straight to voicemail +; +exten => vmx,1,GotoIf($["${ARG2}"="NOMESSAGE"]?s-${ARG2},1) +exten => vmx,n,Set(MODE=${IF($["${ARG2}"="BUSY"]?busy:unavail)}) +exten => vmx,n,GotoIf($["${ARG2}" != "DIRECTDIAL"]?notdirect) +exten => vmx,n,Set(MODE=${IF($["${REGEX("[b]" ${VM_DDTYPE})}" = "1"]?busy:${MODE})}) +exten => vmx,n(notdirect),Noop(Checking if ext ${ARG1} is enabled: ${DB(AMPUSER/${ARG1}/vmx/${MODE}/state)}) +exten => vmx,n,GotoIf($["${DB(AMPUSER/${ARG1}/vmx/${MODE}/state)}" != "enabled"]?s-${ARG2},1) + +; If the required voicemail file does not exist, then abort and go to normal voicemail behavior +; +; TODO: there have been errors using System() with jump to 101 where asterisk works fine at the begining and +; then starts to jump to 101 even on success. This new mode is being tried with the SYSTEM Status which +; returns SUCCESS when the command returned succcessfully with a 0 app return code. +; +exten => vmx,n,Macro(get-vmcontext,${ARG1}) +;exten => vmx,n,TrySystem(/bin/ls ${ASTSPOOLDIR}/voicemail/${VMCONTEXT}/${ARG1}/${MODE}.[wW][aA][vV]) +exten => vmx,n,AGI(checksound.agi,${ASTSPOOLDIR}/voicemail/${VMCONTEXT}/${ARG1}/temp) +exten => vmx,n,GotoIf($["${SYSTEMSTATUS}" = "SUCCESS"]?tmpgreet) +exten => vmx,n,AGI(checksound.agi,${ASTSPOOLDIR}/voicemail/${VMCONTEXT}/${ARG1}/${MODE}) +exten => vmx,n,GotoIf($["${SYSTEMSTATUS}" != "SUCCESS"]?nofile) + +; Get the repeat, timeout and loop times to use if they are overriden form the global settings +; +exten => vmx,n,Set(LOOPCOUNT=0) +exten => vmx,n,GotoIf($["${DB_EXISTS(AMPUSER/${ARG1}/vmx/${MODE}/repeat)}" = "0"]?vmxtime) +exten => vmx,n,Set(VMX_REPEAT=${DB_RESULT}) +exten => vmx,n(vmxtime),GotoIf($["${DB_EXISTS(AMPUSER/${ARG1}/vmx/${MODE}/timeout)}" = "0"]?vmxloops) +exten => vmx,n,Set(VMX_TIMEOUT=${DB_RESULT}) +exten => vmx,n(vmxloops),GotoIf($["${DB_EXISTS(AMPUSER/${ARG1}/vmx/${MODE}/loops)}" = "0"]?vmxanswer) +exten => vmx,n,Set(VMX_LOOPS=${DB_RESULT}) +exten => vmx,n(vmxanswer),Answer() + +; Now play the users voicemail recording as the basis for their ivr, the Read command will repeat as needed and if it timesout +; then we go to the timeout. Otherwise handle invalid options by looping until the limit until a valid option is played. +; +exten => vmx,n(loopstart),Read(ACTION,${ASTSPOOLDIR}/voicemail/${VMCONTEXT}/${ARG1}/${MODE},1,skip,${VMX_REPEAT},${VMX_TIMEOUT}) +exten => vmx,n,GotoIf($["${EXISTS(${ACTION})}" = "1"]?checkopt) + +; If we are here we timed out, go to the required destination +; +exten => vmx,n(noopt),Noop(Timeout: going to timeout dest) +exten => vmx,n,Set(VMX_OPTS=${VMX_OPTS_TIMEOUT}) +exten => vmx,n,GotoIf($["${DB_EXISTS(AMPUSER/${ARG1}/vmx/${MODE}/vmxopts/timeout)}" = "0"]?chktime) +exten => vmx,n,Set(VMX_OPTS=${DB_RESULT}) +exten => vmx,n(chktime),GotoIf($["${DB_EXISTS(AMPUSER/${ARG1}/vmx/${MODE}/timedest/ext)}" = "0"]?dotime) +exten => vmx,n,Set(VMX_TIMEDEST_EXT=${DB_RESULT}) +exten => vmx,n,GotoIf($["${DB_EXISTS(AMPUSER/${ARG1}/vmx/${MODE}/timedest/context)}" = "0"]?timepri) +exten => vmx,n,Set(VMX_TIMEDEST_CONTEXT=${DB_RESULT}) +exten => vmx,n(timepri),GotoIf($["${DB_EXISTS(AMPUSER/${ARG1}/vmx/${MODE}/timedest/pri)}" = "0"]?dotime) +exten => vmx,n,Set(VMX_TIMEDEST_PRI=${DB_RESULT}) +exten => vmx,n(dotime),Goto(${VMX_TIMEDEST_CONTEXT},${VMX_TIMEDEST_EXT},${VMX_TIMEDEST_PRI}) + +; We got an option, check if the option is defined, or one of the system defaults +; +exten => vmx,n(checkopt),GotoIf($["${DB_EXISTS(AMPUSER/${ARG1}/vmx/${MODE}/${ACTION}/ext)}" = "1"]?doopt) +exten => vmx,n,GotoIf($["${ACTION}" = "0"]?o,1) +exten => vmx,n,GotoIf($["${ACTION}" = "*"]?adef,1) + +; Got invalid option loop until the max +; +exten => vmx,n,Set(LOOPCOUNT=$[${LOOPCOUNT} + 1]) +exten => vmx,n,GotoIf($[${LOOPCOUNT} > ${VMX_LOOPS}]?toomany) +exten => vmx,n,Playback(pm-invalid-option&please-try-again) +exten => vmx,n,Goto(loopstart) + +; tomany: to many invalid options, go to the specified destination +; +exten => vmx,n(toomany),Noop(Too Many invalid entries, got to invalid dest) +exten => vmx,n,Set(VMX_OPTS=${VMX_OPTS_LOOPS}) +exten => vmx,n,GotoIf($["${DB_EXISTS(AMPUSER/${ARG1}/vmx/${MODE}/vmxopts/loops)}" = "0"]?chkloop) +exten => vmx,n,Set(VMX_OPTS=${DB_RESULT}) +exten => vmx,n(chkloop),GotoIf($["${DB_EXISTS(AMPUSER/${ARG1}/vmx/${MODE}/loopdest/ext)}" = "0"]?doloop) +exten => vmx,n,Set(VMX_LOOPDEST_EXT=${DB_RESULT}) +exten => vmx,n,GotoIf($["${DB_EXISTS(AMPUSER/${ARG1}/vmx/${MODE}/loopdest/context)}" = "0"]?looppri) +exten => vmx,n,Set(VMX_LOOPDEST_CONTEXT=${DB_RESULT}) ;TODO make configurable per above +exten => vmx,n(looppri),GotoIf($["${DB_EXISTS(AMPUSER/${ARG1}/vmx/${MODE}/loopdest/pri)}" = "0"]?doloop) +exten => vmx,n,Set(VMX_LOOPDEST_PRI=${DB_RESULT}) ;TODO make configurable per above +exten => vmx,n(doloop),Goto(${VMX_LOOPDEST_CONTEXT},${VMX_LOOPDEST_EXT},${VMX_LOOPDEST_PRI}) + +; doopt: execute the valid option that was chosen +; +exten => vmx,n(doopt),Noop(Got a valid option: ${DB_RESULT}) +exten => vmx,n,Set(VMX_EXT=${DB_RESULT}) +; +; Special case, if this option was to go to voicemail, set options and go +; +exten => vmx,n,GotoIf($["${VMX_EXT}" != "dovm"]?getdest) +exten => vmx,n(vmxopts),Set(VMX_OPTS=${VMX_OPTS_DOVM}) +exten => vmx,n,GotoIf($["${DB_EXISTS(AMPUSER/${ARG1}/vmx/${MODE}/vmxopts/dovm)}" = "0"]?vmxdovm) +exten => vmx,n(vmxopts),Set(VMX_OPTS=${DB_RESULT}) +exten => vmx,n(vmxdovm),goto(dovm,1) +; +; General case, setup the goto destination and go there (no error checking, its up to the GUI's to assure +; reasonable values +; +exten => vmx,n(getdest),GotoIf($["${DB_EXISTS(AMPUSER/${ARG1}/vmx/${MODE}/${ACTION}/context)}" = "0"]?vmxpri) +exten => vmx,n,Set(VMX_CONTEXT=${DB_RESULT}) +exten => vmx,n(vmxpri),GotoIf($["${DB_EXISTS(AMPUSER/${ARG1}/vmx/${MODE}/${ACTION}/pri)}" = "0"]?vmxgoto) +exten => vmx,n,Set(VMX_PRI=${DB_RESULT}) +exten => vmx,n(vmxgoto),Goto(${VMX_CONTEXT},${VMX_EXT},${VMX_PRI}) + +; If the required voicemail file is not present, then revert to normal voicemail +; behavior treating as if it was not set +; +exten => vmx,n(nofile),Noop(File for mode: ${MODE} does not exist, SYSTEMSTATUS: ${SYSTEMSTATUS}, going to normal voicemail) +exten => vmx,n,Goto(s-${ARG2},1) +exten => vmx,n(tmpgreet),Noop(Temporary Greeting Detected, going to normal voicemail) +exten => vmx,n,Goto(s-${ARG2},1) + +; Drop into voicemail either as a direct destination (in which case VMX_OPTS might be set to something) or +; if the user timed out or broke out of the loop then VMX_OPTS is always cleared such that an Allison +; message is played and the caller know's what is going on. +; +exten => dovm,1,Noop(VMX Timeout - go to voicemail) +exten => dovm,n,Voicemail(${ARG1}@${VMCONTEXT},${VMX_OPTS}${VMGAIN}) ; no flags, so allison plays please leave ... +exten => dovm,n,Goto(exit-${VMSTATUS},1) + +exten => s-BUSY,1,NoOp(BUSY voicemail) +exten => s-BUSY,n,Macro(get-vmcontext,${ARG1}) +exten => s-BUSY,n,Voicemail(${ARG1}@${VMCONTEXT},${VM_OPTS}b${VMGAIN}) ; Voicemail Busy message +exten => s-BUSY,n,Goto(exit-${VMSTATUS},1) + +exten => s-NOMESSAGE,1,NoOp(NOMESSAGE (beeb only) voicemail) +exten => s-NOMESSAGE,n,Macro(get-vmcontext,${ARG1}) +exten => s-NOMESSAGE,n,Voicemail(${ARG1}@${VMCONTEXT},s${VM_OPTS}${VMGAIN}) +exten => s-NOMESSAGE,n,Goto(exit-${VMSTATUS},1) + +exten => s-DIRECTDIAL,1,NoOp(DIRECTDIAL voicemail) +exten => s-DIRECTDIAL,n,Macro(get-vmcontext,${ARG1}) +exten => s-DIRECTDIAL,n,Voicemail(${ARG1}@${VMCONTEXT},${VM_OPTS}${VM_DDTYPE}${VMGAIN}) +exten => s-DIRECTDIAL,n,Goto(exit-${VMSTATUS},1) + +exten => _s-.,1,Macro(get-vmcontext,${ARG1}) +exten => _s-.,n,Voicemail(${ARG1}@${VMCONTEXT},${VM_OPTS}u${VMGAIN}) ; Voicemail Unavailable message +exten => _s-.,n,Goto(exit-${VMSTATUS},1) + +; If the user has a 0 option defined, use that for operator zero-out from within voicemail +; as well to keep it consistant with the menu structure +; +exten => o,1,Background(one-moment-please) ; 0 during vm message will hangup +exten => o,n,GotoIf($["${DB_EXISTS(AMPUSER/${ARG1}/vmx/${MODE}/0/ext)}" = "0"]?doopdef) + +exten => o,n,Set(VMX_OPDEST_EXT=${DB_RESULT}) +exten => o,n,GotoIf($["${DB_EXISTS(AMPUSER/${ARG1}/vmx/${MODE}/0/context)}" = "1"]?opcontext) +exten => o,n,Set(DB_RESULT=${VMX_CONTEXT}) +exten => o,n(opcontext),Set(VMX_OPDEST_CONTEXT=${DB_RESULT}) +exten => o,n,GotoIf($["${DB_EXISTS(AMPUSER/${ARG1}/vmx/${MODE}/0/pri)}" = "1"]?oppri) +exten => o,n,Set(DB_RESULT=${VMX_PRI}) +exten => o,n(oppri),Set(VMX_OPDEST_PRI=${DB_RESULT}) + +exten => o,n,Goto(${VMX_OPDEST_CONTEXT},${VMX_OPDEST_EXT},${VMX_OPDEST_PRI}) +exten => o,n(doopdef),GotoIf($["x${OPERATOR_XTN}"="x"]?nooper:from-internal,${OPERATOR_XTN},1) +exten => o,n(nooper),GotoIf($["x${FROM_DID}"="x"]?nodid) +exten => o,n,Dial(Local/${FROM_DID)@from-pstn) +exten => o,n,Macro(hangup) +exten => o,n(nodid),Dial(Local/s@from-pstn) +exten => o,n,Macro(hangup) + +; If the user has a * option defined, use that for the * out from within voicemail +; as well to keep it consistant with the menu structure +; +exten => a,1,Macro(get-vmcontext,${ARG1}) +exten => a,n,GotoIf($["${DB_EXISTS(AMPUSER/${ARG1}/vmx/${MODE}/*/ext)}" = "0"]?adef,1) + +exten => a,n,Set(VMX_ADEST_EXT=${DB_RESULT}) +exten => a,n,GotoIf($["${DB_EXISTS(AMPUSER/${ARG1}/vmx/${MODE}/*/context)}" = "1"]?acontext) +exten => a,n,Set(DB_RESULT=${VMX_CONTEXT}) +exten => a,n(acontext),Set(VMX_ADEST_CONTEXT=${DB_RESULT}) +exten => a,n,GotoIf($["${DB_EXISTS(AMPUSER/${ARG1}/vmx/${MODE}/*/pri)}" = "1"]?apri) +exten => a,n,Set(DB_RESULT=${VMX_PRI}) +exten => a,n(apri),Set(VMX_ADEST_PRI=${DB_RESULT}) +exten => a,n,Goto(${VMX_ADEST_CONTEXT},${VMX_ADEST_EXT},${VMX_ADEST_PRI}) + +exten => adef,1,VoiceMailMain(${ARG1}@${VMCONTEXT}) +exten => adef,n,GotoIf($["${ARG3}" = "RETURN"]?exit-RETURN,1) +exten => adef,n,Hangup + +exten => exit-FAILED,1,Playback(im-sorry&an-error-has-occured) +exten => exit-FAILED,n,GotoIf($["${ARG3}" = "RETURN"]?exit-RETURN,1) +exten => exit-FAILED,n,Hangup() + +exten => exit-SUCCESS,1,GotoIf($["${ARG3}" = "RETURN"]?exit-RETURN,1) +exten => exit-SUCCESS,n,Playback(goodbye) +exten => exit-SUCCESS,n,Hangup() + +exten => exit-USEREXIT,1,GotoIf($["${ARG3}" = "RETURN"]?exit-RETURN,1) +exten => exit-USEREXIT,n,Playback(goodbye) +exten => exit-USEREXIT,n,Hangup() + +exten => exit-RETURN,1,Noop(Returning From Voicemail because macro) + +exten => t,1,Hangup() +;------------------------------------------------------------------------ + +;------------------------------------------------------------------------ +; [macro-simple-dial] +;------------------------------------------------------------------------ +; This macro was derived from macro-exten-vm, which is what is normally used to +; ring an extension. It has been simplified and designed to never go to voicemail +; and always return regardless of the DIALSTATUS for any incomplete call. +; +; It's current primary purpose is to allow findmefollow ring an extension prior +; to trying the follow-me ringgroup that is provided. +; +; Ring an extension, if the extension is busy or there is no answer, return +; ARGS: $EXTENSION, $RINGTIME +;------------------------------------------------------------------------ +[macro-simple-dial] +exten => s,1,Set(EXTTOCALL=${ARG1}) +exten => s,n,Set(RT=${ARG2}) +exten => s,n,Set(CFUEXT=${DB(CFU/${EXTTOCALL})}) +exten => s,n,Set(CFBEXT=${DB(CFB/${EXTTOCALL})}) + +exten => s,n,Macro(dial,${RT},${DIAL_OPTIONS},${EXTTOCALL}) + +exten => s,n,Set(PR_DIALSTATUS=${DIALSTATUS}) + +; if we return, thus no answer, and they have a CFU setting, then we try that next +; +exten => s,n,GosubIf($[$["${PR_DIALSTATUS}"="NOANSWER"] & $["foo${CFUEXT}"!="foo"]]?docfu,1) ; check for CFU in use on no answer +exten => s,n,GosubIf($[$["${PR_DIALSTATUS}"="BUSY"] & $["foo${CFBEXT}"!="foo"]]?docfb,1) ; check for CFB in use on busy +exten => s,n,Set(DIALSTATUS=${PR_DIALSTATUS}) + +; Nothing yet, then go to the end (which will just return, but in case we decide to do something with certain +; return situations, this is left in. +; +exten => s,n,Goto(s-${DIALSTATUS},1) + +; Try the Call Forward on No Answer / Unavailable number. +; We want to try CFU if set, but we want the same ring timer as was set to our call (or do we want the +; system ringtimer? - probably not). Then if no answer there (assuming it doesn't drop into their vm or +; something we return, which will have the net effect of returning to the followme setup.) +; +; want to avoid going to other follow-me settings here. So check if the CFUEXT is a user and if it is +; then direct it straight to ext-local (to avoid getting intercepted by findmefollow) otherwise send it +; to from-internal since it may be an outside line. +; +exten => docfu,1,GotoIf( $[ "foo${DB(AMPUSER/${CFUEXT}/device)}" = "foo" ]?chlocal) +exten => docfu,n,Dial(Local/${CFUEXT}@ext-local,${RT},${DIAL_OPTIONS}) +exten => docfu,n,Return +exten => docfu,n(chlocal),Dial(Local/${CFUEXT}@from-internal/n,${RT},${DIAL_OPTIONS}) +exten => docfu,n,Return + +; Try the Call Forward on Busy number +exten => docfb,1,GotoIf( $[ "foo${DB(AMPUSER/${CFBEXT}/device)}" = "foo" ]?chlocal) +exten => docfb,n,Dial(Local/${CFBEXT}@ext-local,${RT},${DIAL_OPTIONS}) +exten => docfb,n,Return +exten => docfb,n(chlocal),Dial(Local/${CFBEXT}@from-internal/n,${RT},${DIAL_OPTIONS}) +exten => docfb,n,Return + +; In all cases of no connection, come here and simply return, since the calling dialplan will +; decide what to do next +exten => _s-.,1,NoOp(Extension is reporting ${EXTEN}) +;------------------------------------------------------------------------ + + +; get the voicemail context for the user in ARG1 +[macro-get-vmcontext] +exten => s,1,Set(VMCONTEXT=${DB(AMPUSER/${ARG1}/voicemail)}) +exten => s,2,GotoIf($["foo${VMCONTEXT}" = "foo"]?200:300) +exten => s,200,Set(VMCONTEXT=default) +exten => s,300,NoOp() + +; For some reason, if I don't run setCIDname, CALLERID(name) will be blank in my AGI +; ARGS: none +[macro-fixcid] +exten => s,1,Set(CALLERID(name)=${CALLERID(name)}) + +; Ring groups of phones +; ARGS: comma separated extension list +; 1 - Ring Group Strategy +; 2 - ringtimer +; 3 - prefix +; 4 - extension list +[macro-rg-group] +exten => s,1,Macro(user-callerid,SKIPTTL) ; already called from ringgroup +exten => s,2,GotoIf($["${CALLERID(name):0:${LEN(${RGPREFIX})}}" != "${RGPREFIX}"]?4:3) ; check for old prefix +exten => s,3,Set(CALLERID(name)=${CALLERID(name):${LEN(${RGPREFIX})}}) ; strip off old prefix +exten => s,4,Set(RGPREFIX=${ARG3}) ; set new prefix +exten => s,5,Set(CALLERID(name)=${RGPREFIX}${CALLERID(name)}) ; add prefix to callerid name +exten => s,6,Set(RecordMethod=Group) ; set new prefix +exten => s,7,Macro(record-enable,${MACRO_EXTEN},${RecordMethod}) +exten => s,8,Set(RingGroupMethod=${ARG1}) ; +exten => s,9,Macro(dial,${ARG2},${DIAL_OPTIONS},${ARG4}) +exten => s,10,Set(RingGroupMethod='') ; + + +; +; Outgoing channel(s) are busy ... inform the client +; but use noanswer features like ringgroups don't break by being answered +; just to play the message. +; +[macro-outisbusy] +exten => s,1,Playback(all-circuits-busy-now,noanswer) +exten => s,n,Playback(pls-try-call-later,noanswer) +exten => s,n,Macro(hangupcall) + +; What to do on hangup. +[macro-hangupcall] +exten => s,1,ResetCDR(w) +exten => s,n,NoCDR() + +; Cleanup any remaining RG flag +; +exten => s,n,GotoIf($[ "x${USE_CONFIRMATION}" = "x" | "x${RINGGROUP_INDEX}" = "x" | "${CHANNEL}" != "${UNIQCHAN}"]?skiprg) +exten => s,n,Noop(Cleaning Up Confirmation Flag: RG/${RINGGROUP_INDEX}/${CHANNEL}) +exten => s,n,DBDel(RG/${RINGGROUP_INDEX}/${CHANNEL}) + +; Cleanup any remaining BLKVM flag +; +exten => s,n(skiprg),GotoIf($[ "x${BLKVM_BASE}" = "x" | "BLKVM/${BLKVM_BASE}/${CHANNEL}" != "${BLKVM_OVERRIDE}" ]?skipblkvm) +exten => s,n,Noop(Cleaning Up Block VM Flag: ${BLKVM_OVERRIDE}) +exten => s,n,DBDel(${BLKVM_OVERRIDE}) + +; Cleanup any remaining FollowMe DND flags +; +exten => s,n(skipblkvm),GotoIf($[ "x${FMGRP}" = "x" | "x${FMUNIQUE}" = "x" | "${CHANNEL}" != "${FMUNIQUE}" ]?theend) +exten => s,n,DBDel(FM/DND/${FMGRP}/${CHANNEL}) + +exten => s,n(theend),Hangup + +[macro-faxreceive] +exten => s,1,Set(FAXFILE=${ASTSPOOLDIR}/fax/${UNIQUEID}.tif) +exten => s,2,Set(EMAILADDR=${FAX_RX_EMAIL}) +exten => s,3,rxfax(${FAXFILE}) +exten => s,103,Set(EMAILADDR=${FAX_RX_EMAIL}) +exten => s,104,Goto(3) + +; dialout and strip the prefix +[macro-dialout] +exten => s,1,Macro(user-callerid,SKIPTTL) +exten => s,2,GotoIf($["${ECID${CALLERID(number)}}" = ""]?5) ;check for CID override for exten +exten => s,3,Set(CALLERID(all)=${ECID${CALLERID(number)}}) +exten => s,4,Goto(7) +exten => s,5,GotoIf($["${OUTCID_${ARG1}}" = ""]?7) ;check for CID override for trunk +exten => s,6,Set(CALLERID(all)=${OUTCID_${ARG1}}) +exten => s,7,Set(length=${LEN(${DIAL_OUT_${ARG1}})}) +exten => s,8,Dial(${OUT_${ARG1}}/${ARG2:${length}}) +exten => s,9,Playtones(congestion) +exten => s,10,Congestion(5) +exten => s,109,Macro(outisbusy) + + +; dialout using default OUT trunk - no prefix +[macro-dialout-default] +exten => s,1,Macro(user-callerid,SKIPTTL) +exten => s,2,Macro(record-enable,${CALLERID(number)},OUT) +exten => s,3,Macro(outbound-callerid,${ARG1}) +exten => s,4,Dial(${OUT}/${ARG1}) +exten => s,5,Playtones(congestion) +exten => s,6,Congestion(5) +exten => s,105,Macro(outisbusy) + +[macro-dialout-trunk-predial-hook] +; this macro intentially left blank so it may be safely overwritten for any custom +; requirements that an installatin may have. +; +; MACRO RETURN CODE: ${PREDIAL_HOOK_RET} +; if set to "BYPASS" then this trunk will be skipped +; +exten => s,1,MacroExit() + +; This macro is for dev purposes and just dumps channel/app variables. Useful when designing new contexts. +[macro-dumpvars] +exten => s,1,Noop(ACCOUNTCODE=${ACCOUNTCODE}) +exten => s,2,Noop(ANSWEREDTIME=${ANSWEREDTIME}) +exten => s,3,Noop(BLINDTRANSFER=${BLINDTRANSFER}) +exten => s,4,Noop(CALLERID=${CALLERID(all)}) +exten => s,5,Noop(CALLERID(name)=${CALLERID(name)}) +exten => s,6,Noop(CALLERID(number)=${CALLERID(number)}) +exten => s,7,Noop(CALLINGPRES=${CALLINGPRES}) +exten => s,8,Noop(CHANNEL=${CHANNEL}) +exten => s,9,Noop(CONTEXT=${CONTEXT}) +exten => s,10,Noop(DATETIME=${DATETIME}) +exten => s,11,Noop(DIALEDPEERNAME=${DIALEDPEERNAME}) +exten => s,12,Noop(DIALEDPEERNUMBER=${DIALEDPEERNUMBER}) +exten => s,13,Noop(DIALEDTIME=${DIALEDTIME}) +exten => s,14,Noop(DIALSTATUS=${DIALSTATUS}) +exten => s,15,Noop(DNID=${DNID}) +exten => s,16,Noop(EPOCH=${EPOCH}) +exten => s,17,Noop(EXTEN=${EXTEN}) +exten => s,18,Noop(HANGUPCAUSE=${HANGUPCAUSE}) +exten => s,19,Noop(INVALID_EXTEN=${INVALID_EXTEN}) +exten => s,20,Noop(LANGUAGE=${LANGUAGE}) +exten => s,21,Noop(MEETMESECS=${MEETMESECS}) +exten => s,22,Noop(PRIORITY=${PRIORITY}) +exten => s,23,Noop(RDNIS=${RDNIS}) +exten => s,24,Noop(SIPDOMAIN=${SIPDOMAIN}) +exten => s,25,Noop(SIP_CODEC=${SIP_CODEC}) +exten => s,26,Noop(SIPCALLID=${SIPCALLID}) +exten => s,27,Noop(SIPUSERAGENT=${SIPUSERAGENT}) +exten => s,29,Noop(TXTCIDNAME=${TXTCIDNAME}) +exten => s,30,Noop(UNIQUEID=${UNIQUEID}) +exten => s,31,Noop(TOUCH_MONITOR=${TOUCH_MONITOR}) +exten => s,32,Noop(MACRO_CONTEXT=${MACRO_CONTEXT}) +exten => s,33,Noop(MACRO_EXTEN=${MACRO_EXTEN}) +exten => s,34,Noop(MACRO_PRIORITY=${MACRO_PRIORITY}) + +[macro-user-logon] +; check device type +; +exten => s,1,Set(DEVICETYPE=${DB(DEVICE/${CALLERID(number)}/type)}) +exten => s,n,Answer() +exten => s,n,Wait(1) +exten => s,n,GotoIf($["${DEVICETYPE}" = "fixed"]?s-FIXED,1) +; get user's extension +; +exten => s,n,Set(AMPUSER=${ARG1}) +exten => s,n,GotoIf($["${AMPUSER}" != ""]?gotpass) +exten => s,n(playagain),Read(AMPUSER,please-enter-your-extension-then-press-pound,,,4) +; get user's password and authenticate +; +exten => s,n,GotoIf($["${AMPUSER}" = ""]?s-MAXATTEMPTS,1) +exten => s,n(gotpass),GotoIf($["${DB_EXISTS(AMPUSER/${AMPUSER}/password)}" = "0"]?s-NOUSER,1) +exten => s,n,Set(AMPUSERPASS=${DB_RESULT}) +exten => s,n,GotoIf($[${LEN(${AMPUSERPASS})} = 0]?s-NOPASSWORD,1) +; do not continue if the user has already logged onto this device +; +exten => s,n,Set(DEVICEUSER=${DB(DEVICE/${CALLERID(number)}/user)}) +exten => s,n,GotoIf($["${DEVICEUSER}" = "${AMPUSER}"]?s-ALREADYLOGGEDON,1) +exten => s,n,Authenticate(${AMPUSERPASS}) +exten => s,n,DeadAGI(user_login_out.agi,login,${CALLERID(number)},${AMPUSER}) +exten => s,n,Playback(vm-goodbye) + +exten => s-FIXED,1,NoOp(Device is FIXED and cannot be logged into) +exten => s-FIXED,n,Playback(ha/phone) +exten => s-FIXED,n,SayDigits(${CALLERID(number)}) +exten => s-FIXED,n,Playback(is-curntly-unavail&vm-goodbye) +exten => s-FIXED,n,Hangup ;TODO should play msg indicated device cannot be logged into + +exten => s-ALREADYLOGGEDON,1,NoOp(This device has already been logged into by this user) +exten => s-ALREADYLOGGEDON,n,Playback(vm-goodbye) +exten => s-ALREADYLOGGEDON,n,Hangup ;TODO should play msg indicated device is already logged into + +exten => s-NOPASSWORD,1,NoOp(This extension does not exist or no password is set) +exten => s-NOPASSWORD,n,Playback(pbx-invalid) +exten => s-NOPASSWORD,n,Goto(s,playagain) + +exten => s-MAXATTEMPTS,1,NoOp(Too many login attempts) +exten => s-MAXATTEMPTS,n,Playback(vm-goodbye) +exten => s-MAXATTEMPTS,n,Hangup + +exten => s-NOUSER,1,NoOp(Invalid extension ${AMPUSER} entered) +exten => s-NOUSER,n,Playback(pbx-invalid) +exten => s-NOUSER,n,Goto(s,playagain) + +[macro-user-logoff] +; check device type +; +exten => s,1,Set(DEVICETYPE=${DB(DEVICE/${CALLERID(number)}/type)}) +exten => s,n,GotoIf($["${DEVICETYPE}" = "fixed"]?s-FIXED,1) +exten => s,n,DeadAGI(user_login_out.agi,logout,${CALLERID(number)}) +exten => s,n(done),Playback(vm-goodbye) + +exten => s-FIXED,1,NoOp(Device is FIXED and cannot be logged out of) +exten => s-FIXED,n,Playback(an-error-has-occured&vm-goodbye) +exten => s-FIXED,n,Hangup ;TODO should play msg indicated device cannot be logged into + + + +; Privacy Manager Macro makes sure that any calls that don't pass the privacy manager are presented +; with congestion since there have been observed cases of the call continuing if not stopped with a +; congestion, and this provides a slightly more friendly 'sorry' message in case the user is +; legitamately trying to be cooperative. +; +; Note: the following options are configurable in privacy.conf: +; +; maxretries = 3 ; default value, number of retries before failing +; minlength = 10 ; default value, number of digits to be accepted as valid CID +; +[macro-privacy-mgr] +exten => s,1,Set(KEEPCID=${CALLERID(num)}) +exten => s,n,GotoIf($["foo${CALLERID(num):0:1}"="foo+"]?CIDTEST2:CIDTEST1) +exten => s,n(CIDTEST1),Set(TESTCID=${MATH(1+${CALLERID(num)})}) +exten => s,n,Goto(TESTRESULT) +exten => s,n(CIDTEST2),Set(TESTCID=${MATH(1+${CALLERID(num):1})}) +exten => s,n(TESTRESULT),GotoIf($["foo${TESTCID}"="foo"]?CLEARCID:PRIVMGR) +exten => s,n(CLEARCID),Set(CALLERID(num)=) +exten => s,n(PRIVMGR),PrivacyManager() +exten => s,n,GotoIf($["${PRIVACYMGRSTATUS}"="FAILED"]?fail) +exten => s,n,SetCallerPres(allowed_passed_screen); stop gap until app_privacy.c clears unavailble bit +exten => s,PRIVMGR+101(fail),Noop(STATUS: ${PRIVACYMGRSTATUS} CID: ${CALLERID(num)} ${CALLERID(name)} CALLPRES: ${CALLLINGPRES}) +exten => s,n,Playback(sorry-youre-having-problems) +exten => s,n,Playback(goodbye) +exten => s,n,Playtones(congestion) +exten => s,n,Congestion(5) + + + +; Text-To-Speech related macros +; These all follow common actions. First try to playback a file "tts/custom-md5" +; where "md5" is the md5() of whatever is going to be played. If that doesn't exist, +; try to playback using macro-tts-sayXXXXX (where XXXXX is text/digits/etc, same as +; the macro below). If that macro exits with MACRO_OFFSET=100, then it's done, +; therwise, fallback to the default asterisk method. +; +; say text is purely for text-to-speech, there is no fallback +[macro-saytext] +exten => s,1,Noop(Trying custom SayText playback for "${ARG1}") +exten => s,n,Playback(tts/custom-${MD5(${ARG1})}) +exten => s,n,GotoIf($["${PLAYBACKSTATUS}"="SUCCESS"]?done) +; call tts-saytext. This should set MACRO_OFFSET=101 if it was successful +exten => s,n(tts),Macro(tts-saytext,${ARG1},${ARG2},${ARG3}) +exten => s,n,Noop(No text-to-speech handler for SayText, cannot say "${ARG1}") +exten => s,n,Goto(done) +exten => s,tts+101,Noop(tts handled saytext) + +; say name is for saying names typically, but fallsback to using SayAlpha +; (saying the word letter-by-letter) +[macro-sayname] +exten => s,1,Noop(Trying custom SayName playback for "${ARG1}") +exten => s,n,Playback(tts/custom-${MD5(${ARG1})}) +exten => s,n,GotoIf($["${PLAYBACKSTATUS}"="SUCCESS"]?done) +; call tts-sayalpha. This should set MACRO_OFFSET=101 if it was successful +exten => s,n(tts),Macro(tts-sayalpha,${ARG1},${ARG2},${ARG3}) +exten => s,n,SayAlpha(${ARG1}) +exten => s,n,Goto(done) +exten => s,tts+101,Noop(tts handled sayname) + +; Say number is for saying numbers (eg "one thousand forty six") +[macro-saynumber] +exten => s,1,Noop(Trying custom SayNumber playback for "${ARG1}") +exten => s,n,Playback(tts/custom-${MD5(${ARG1})}) +exten => s,n,GotoIf($["${PLAYBACKSTATUS}"="SUCCESS"]?done) +; call tts-saynumber. This should set MACRO_OFFSET=101 if it was successful +exten => s,n(tts),Macro(tts-saynumber,${ARG1},${ARG2},${ARG3}) +exten => s,n,SayNumber(${ARG1}) +exten => s,n,Goto(done) +exten => s,tts+101,Noop(tts handled saynumber) + +; Say digits is for saying digits one-by-one (eg, "one zero four six") +[macro-saydigits] +exten => s,1,Noop(Trying custom SayDigits playback for "${ARG1}") +exten => s,n,Playback(tts/custom-${MD5(${ARG1})}) +exten => s,n,GotoIf($["${PLAYBACKSTATUS}"="SUCCESS"]?done) +; call tts-saydigits. This should set MACRO_OFFSET=101 if it was successful +exten => s,n(tts),Macro(tts-saydigits,${ARG1},${ARG2},${ARG3}) +exten => s,n,SayDigits(${ARG1}) +exten => s,n,Goto(done) + + +; +; ############################################################################ +; Inbound Contexts [from] +; ############################################################################ + +[from-sip-external] +;give external sip users congestion and hangup +; Yes. This is _really_ meant to be _. - I know asterisk whinges about it, but +; I do know what I'm doing. This is correct. +exten => _.,1,NoOp(Received incoming SIP connection from unknown peer to ${EXTEN}) +exten => _.,n,Set(DID=${IF($["${EXTEN:1:2}"=""]?s:${EXTEN})}) +exten => _.,n,Goto(s,1) +exten => s,1,GotoIf($["${ALLOW_SIP_ANON}"="yes"]?from-trunk,${DID},1) +exten => s,n,Set(TIMEOUT(absolute)=15) +exten => s,n,Answer +exten => s,n,Wait(2) +exten => s,n,Playback(ss-noservice) +exten => s,n,Playtones(congestion) +exten => s,n,Congestion(5) +exten => h,1,NoOp(Hangup) +exten => i,1,NoOp(Invalid) +exten => t,1,NoOp(Timeout) + +[from-internal-xfer] +; applications are now mostly all found in from-internal-additional in _custom.conf +include => parkedcalls +include => from-internal-custom +;allow phones to dial other extensions +include => ext-fax +;allow phones to access generated contexts +; +; MODIFIED (PL) +; +; Currently the include for findmefollow is being auto-generated before ext-local which is the desired behavior. +; However, I haven't been able to do anything that I know of to force this. We need to determine if it should +; be hardcoded into here to make sure it doesn't change with some configuration. For now I will leave it out +; until we can discuss this. +; +include => ext-local-confirm +include => findmefollow-ringallv2 +include => from-internal-additional +; This causes grief with '#' transfers, commenting out for the moment. +; include => bad-number +exten => s,1,Macro(hangupcall) +exten => h,1,Macro(hangupcall) + +[from-internal] +include => from-internal-xfer +include => bad-number + +;------------------------------------------------------------------------ +; [macro-setmusic] +;------------------------------------------------------------------------ +; CONTEXT: macro-setmusic +; PURPOSE: to turn off moh on routes where it is not desired +; +;------------------------------------------------------------------------ +[macro-setmusic] +exten => s,1,NoOp(Setting Outbound Route MoH To: ${ARG1}) +exten => s,2,SetMusicOnHold(${ARG1}) +;------------------------------------------------------------------------ + +; ########################################## +; ## Ring Groups with Confirmation macros ## +; ########################################## +; Used by followme and ringgroups + +;------------------------------------------------------------------------ +; [macro-dial-confirm] +;------------------------------------------------------------------------ +; This has now been incorporated into dialparties. It still only works with ringall +; and ringall-prim strategies. Have not investigated why it doesn't work with +; hunt and memory hunt. +; +;------------------------------------------------------------------------ +[macro-dial-confirm] +; This was written to make it easy to use macro-dial-confirm instead of macro-dial in generated dialplans. +; This takes the same paramaters, with an additional paramater of the ring group Number +; ARG1 is the timeout +; ARG2 is the DIAL_OPTIONS +; ARG3 is a list of xtns to call - 203-222-240-123123123#-211 +; ARG4 is the ring group number + +; This sets a unique value to indicate that the channel is ringing. This is used for warning slow +; users that the call has already been picked up. +; +exten => s,1,Set(DB(RG/${ARG4}/${CHANNEL})=RINGING) + +; We need to keep that channel variable, because it'll change when we do this dial, so set it to +; fallthrough to every sibling. +; +exten => s,n,Set(__UNIQCHAN=${CHANNEL}) + +; The calling ringgroup should have set RingGroupMethod appropriately. We need to set two +; additional parameters: +; +; USE_CONFIRMATION, RINGGROUP_INDEX +; +; Thse are passed to inform dialparties to place external calls through the [grps] context +; +exten => s,n,Set(USE_CONFIRMATION=TRUE) +exten => s,n,Set(RINGGROUP_INDEX=${ARG4}) +exten => s,n,Set(ARG4=) ; otherwise it gets passed to dialparties.agi which processes it (prob bug) + +exten => s,n,Macro(dial,${ARG1},${ARG2},${ARG3}) + +; delete the variable, if we are here, we are done trying to dial and it may have been left around +; +exten => s,n,DBDel(RG/${RINGGROUP_INDEX}/${CHANNEL}) +exten => s,n,Set(USE_CONFIRMATION=) +exten => s,n,Set(RINGGROUP_INDEX=) +;------------------------------------------------------------------------ + +;------------------------------------------------------------------------ +; [macro-auto-confirm] +;------------------------------------------------------------------------ +; This macro is called from ext-local-confirm to auto-confirm a call so that other extensions +; are aware that the call has been answered. +; +;------------------------------------------------------------------------ +[macro-auto-confirm] +exten => s,1,Set(__MACRO_RESULT=) +exten => s,n,Set(__CWIGNORE=) +exten => s,n,DBDel(${BLKVM_OVERRIDE}) +exten => s,n,DBDel(RG/${ARG1}/${UNIQCHAN}) + +;------------------------------------------------------------------------ +; [macro-auto-blkvm] +;------------------------------------------------------------------------ +; This macro is called for any extension dialed form a queue, ringgroup +; or followme, so that the answering extension can clear the voicemail block +; override allow subsequent transfers to properly operate. +; +;------------------------------------------------------------------------ +[macro-auto-blkvm] +exten => s,1,Set(__MACRO_RESULT=) +exten => s,n,Set(__CWIGNORE=) +exten => s,n,DBDel(${BLKVM_OVERRIDE}) + +;------------------------------------------------------------------------ +; [ext-local-confirm] +;------------------------------------------------------------------------ +; If call confirm is being used in a ringgroup, then calls that do not require confirmation are sent +; to this extension instead of straight to the device. +; +; The sole purpose of sending them here is to make sure we run Macro(auto-confirm) if this +; extension answers the line. This takes care of clearing the database key that is used to inform +; other potential late comers that the extension has been answered by someone else. +; +; ALERT_INFO is deprecated in Asterisk 1.4 but still used throughout the FreePBX dialplan and +; usually set by dialparties.agi. This allows ineritance. Since no dialparties.agi here, set the +; header if it is set. +; +;------------------------------------------------------------------------ +[ext-local-confirm] +exten => _LC-.,1,Noop(IN ext-local-confirm with - RT: ${RT}, RG_IDX: ${RG_IDX}) +exten => _LC-.,n,GotoIf($["x${ALERT_INFO}"="x"]?godial) +exten => _LC-.,n,SIPAddHeader(Alert-Info: ${ALERT_INFO}) +exten => _LC-.,n(godial),dial(${DB(DEVICE/${EXTEN:3}/dial)},${RT},M(auto-confirm^${RG_IDX})${DIAL_OPTIONS}) + +;------------------------------------------------------------------------ +; [findmefollow-ringallv2] +;------------------------------------------------------------------------ +; This context, to be included in from-internal, implements the PreRing part of findmefollow +; as well as the GroupRing part. It also communicates between the two so that if DND is set +; on the primary extension, and mastermode is enabled, then the other extensions will not ring +; +;------------------------------------------------------------------------ +[findmefollow-ringallv2] +exten => _FMPR-.,1,Noop(In FMPR ${FMGRP} with ${EXTEN:5}) +exten => _FMPR-.,n,Set(RingGroupMethod=) +exten => _FMPR-.,n,Set(USE_CONFIRMATION=) +exten => _FMPR-.,n,Set(RINGGROUP_INDEX=) +exten => _FMPR-.,n,Macro(simple-dial,${EXTEN:5},${FMREALPRERING}) +exten => _FMPR-.,n,GotoIf($["${DIALSTATUS}" != "BUSY"]?nodnd) +exten => _FMPR-.,n,Set(DB(FM/DND/${FMGRP}/${FMUNIQUE})=DND) +exten => _FMPR-.,n(nodnd),Noop(Ending FMPR ${FMGRP} with ${EXTEN:5} and dialstatus ${DIALSTATUS}) +exten => _FMPR-.,n,Hangup() + +exten => _FMGL-.,1,Noop(In FMGL ${FMGRP} with ${EXTEN:5}) +exten => _FMGL-.,n,GotoIf($["${DB(FM/DND/${FMGRP}/${FMUNIQUE})}" = "DND"]?dodnd) +exten => _FMGL-.,n,Wait(1) +exten => _FMGL-.,n,GotoIf($["${DB(FM/DND/${FMGRP}/${FMUNIQUE})}" = "DND"]?dodnd) +exten => _FMGL-.,n,Wait(1) +exten => _FMGL-.,n,GotoIf($["${DB(FM/DND/${FMGRP}/${FMUNIQUE})}" = "DND"]?dodnd) +exten => _FMGL-.,n,Wait(${FMPRERING}) +exten => _FMGL-.,n,GotoIf($["${DB(FM/DND/${FMGRP}/${FMUNIQUE})}" = "DND"]?dodnd) +exten => _FMGL-.,n,DBDel(FM/DND/${FMGRP}/${FMUNIQUE}) +exten => _FMGL-.,n(dodial),Macro(dial,${FMGRPTIME},${DIAL_OPTIONS},${EXTEN:5}) +exten => _FMGL-.,n,Noop(Ending FMGL ${FMGRP} with ${EXTEN:5} and dialstatus ${DIALSTATUS}) +exten => _FMGL-.,n,Hangup() +exten => _FMGL-.,n+10(dodnd),DBDel(FM/DND/${FMGRP}/${FMUNIQUE}) +exten => _FMGL-.,n,GotoIf($["${FMPRIME}" = "FALSE"]?dodial) +exten => _FMGL-.,n,Noop(Got DND in FMGL ${FMGRP} with ${EXTEN:5} in ${RingGroupMethod} mode, aborting) +exten => _FMGL-.,n,Hangup() + +;------------------------------------------------------------------------ +; [block-cf] +;------------------------------------------------------------------------ +; This context is set as a target with FORWARD_CONTEXT when Call Forwarding is set to be +; ignored in a ringgoup or other features that may take advantage of this. Server side +; CF is done in dialparties.agi but if a client device forwards a call, it will be caught +; and blocked here. +;------------------------------------------------------------------------ +[block-cf] +exten => _X.,1,Noop(Blocking callforward to ${EXTEN} because CF is blocked) +exten => _X.,n,Hangup() + +;------------------------------------------------------------------------ + +; ############################################################################ +; Extension Contexts [ext] +; ############################################################################ + +[ext-fax] +exten => s,1,Answer +exten => s,2,Goto(in_fax,1) +exten => in_fax,1,StopPlayTones +exten => in_fax,2,GotoIf($["${FAX_RX}" = "system"]?3:analog_fax,1) +exten => in_fax,3,Macro(faxreceive) +exten => in_fax,4,Hangup +exten => analog_fax,1,GotoIf($["${FAX_RX}" = "disabled"]?4:2) ;if fax is disabled, just hang up +exten => analog_fax,2,Set(DIAL=${DB(DEVICE/${FAX_RX}/dial)}); +exten => analog_fax,3,Dial(${DIAL},20,d) +exten => analog_fax,4,Hangup +;exten => out_fax,1,wait(7) +exten => out_fax,1,txfax(${TXFAX_NAME},caller) +exten => out_fax,2,Hangup +exten => h,1,system(/var/lib/asterisk/bin/fax-process.pl --to ${EMAILADDR} --from ${FAX_RX_FROM} --dest "${FROM_DID}" --subject "Fax from ${URIENCODE(${CALLERID(number)})} ${URIENCODE(${CALLERID(name)})}" --attachment fax_${URIENCODE(${CALLERID(number)})}.pdf --type application/pdf --file ${FAXFILE}); +exten => h,2,Hangup() + +;this is where parked calls go if they time-out. Should probably re-ring +[default] +include => ext-local +exten => s,1,Playback(vm-goodbye) +exten => s,2,Macro(hangupcall) + Index: /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/etc/iax.conf =================================================================== --- /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/etc/iax.conf (revision 5450) +++ /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/etc/iax.conf (revision 5450) @@ -0,0 +1,17 @@ +; do not edit this file, this is an auto-generated file by freepbx +; all modifications must be done from the web gui + +[general] + +; These will all be included in the [general] context +#include iax_general_additional.conf +#include iax_general_custom.conf +#include iax_registrations_custom.conf +#include iax_registrations.conf + +; These should all be expected to come after the [general] context +; +#include iax_custom.conf +#include iax_additional.conf +#include iax_custom_post.conf + Index: /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/page.general.php =================================================================== --- /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/page.general.php (revision 6589) +++ /freepbx/tags/2.5.0rc3/amp_conf/htdocs/admin/modules/core/page.general.php (revision 6589) @@ -0,0 +1,501 @@ +prepare('UPDATE globals SET value = ? WHERE variable = ?'); + //$compiled = $db->prepare('REPLACE INTO globals (value,variable) VALUES (?, ?)'); + $result = $db->executeMultiple($compiled,$globalfields); + if(DB::IsError($result)) { + echo $action.'
'; + die_freepbx($result->getMessage()); + } + + /* update online updates and email as appropriate + */ + $online_updates = isset($_REQUEST['online_updates'])? $_REQUEST['online_updates'] : 'yes'; + $update_email = isset($_REQUEST['update_email']) ? $_REQUEST['update_email'] : ''; + + if ($online_updates == 'yes') { + $cm->enable_updates(); + } else { + $cm->disable_updates(); + } + + // TODO: maybe check the email address a bit better server/client side + // + $cm->save_email($update_email); + + //indicate 'need reload' link in header.php + needreload(); +} + +$online_updates = $cm->updates_enabled() ? 'yes' : 'no'; +$update_email = $cm->get_email(); + +//get all rows relating to selected account +$sql = "SELECT * FROM globals"; +$globals = $db->getAll($sql); +if(DB::IsError($globals)) { +die_freepbx($globals->getMessage()); +} + +//create a set of variables that match the items in global[0] +foreach ($globals as $global) { + ${trim($global[0])} = $global[1]; +} + +?> + +
+ + + +
+ + + +
+ +
+
+
+
+
+
+
+ +
+ +
+
+
+
+
+
+
+ +
+ +
+ + + + + +
+ + + + +
+ + + + + +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+ + + + + + +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +

+
+
+ +
+ + + tabindex=""> +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +   
+ + +     
+ + +     
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ +
+ + + + +
+ + + +
+ + + tabindex=""> +
+ + + + + +
+ +
+ + + + +
+ Selecting 'disabled' will result in incoming calls being answered more quickly.")?> + + + +
+ + + +
+ + + +
+
+ + + +
+ : + + +
+ : + + +
+
+ + +
+ +

+

+
+
: +
+ +
+ +
+ + + + + + + + + +
+ + This will transmit your FreePBX and Asterisk version numbers along with a unique but random identifier. This is used to provide proper update information and to track version usage to focus development and maintenance efforts. No private information is transmitted."); ?> + + +
+ + + + +
+
+ " tabindex=""> +
+ +
+