getAll($sql);
if(DB::IsError($results)) {
$results = null;
}
return isset($results)?$results[0][0]:null;
}
}
//used to get module information
function customcontexts_setmodulevalue($id,$value) {
global $db;
$sql = "update customcontexts_module set value = '$value' where id = '$id'";
$db->query($sql);
}
//after dialplan is created and ready for injection, we grab the includes of any context the user added in admin
function customcontexts_hookGet_config($engine) {
global $db;
global $ext;
switch($engine) {
case 'asterisk':
$sql = 'UPDATE customcontexts_includes_list SET missing = 1 WHERE context
NOT IN (SELECT context FROM customcontexts_contexts_list WHERE locked = 1)';
$db->query($sql);
$sql = 'SELECT context FROM customcontexts_contexts_list';
$sections = $db->getAll($sql);
if(DB::IsError($results)) {
$results = null;
}
foreach ($sections as $section) {
$section = $section[0];
$i = 0;
if (isset($ext->_includes[$section])) {
foreach ($ext->_includes[$section] as $include) {
$i = $i + 1;
if ($section == 'outbound-allroutes') {
$sql = 'INSERT INTO customcontexts_includes_list
(context, include, description, missing, sort)
VALUES ("'.$section.'", "'.$include['include'].'",
"'.$include['comment'].'", "0", "'.($i+100).'")
ON DUPLICATE KEY UPDATE sort = "'.($i+100).'", missing = "0"';
$db->query($sql);
} else {
$sql = 'UPDATE customcontexts_includes_list SET missing = "0", sort = "'.$i.'"
WHERE context = "'.$section.'" and include = "'.$include['include'].'"';
$db->query($sql);
}
$sql = 'INSERT IGNORE INTO customcontexts_includes_list
(context, include, description, sort)
VALUES ("'.$section.'", "'.$include['include'].'",
"'.$include['include'].'", "'.$i.'")';
$db->query($sql);
}
}
}
$sql = "delete from customcontexts_includes_list where missing = 1";
$db->query($sql);
break;
}
}
// provide hook for routing
function customcontexts_hook_core($viewing_itemid, $target_menuid) {
switch ($target_menuid) {
// only provide display for outbound routing
case 'routing':
/*$route = substr($viewing_itemid,4);$hookhtml = '';return $hookhtml;*/
return '';
break;
case 'devices':
case 'extensions':
if((isset($_REQUEST['tech_hardware']) && !empty($_REQUEST['tech_hardware'])) || (isset($_REQUEST['extdisplay']) && !empty($_REQUEST['extdisplay']))){
return load_view(__DIR__.'/views/extensions_hook.php');
}
break;
default:
return false;
break;
}
}
//this lists all includes from the sql database (for the requsted context) which we parsed out of the dialplan
//from any contexts the user entered in admin - information was saved to database on the last reload
function customcontexts_getincludeslist($context) {
global $db;
// $sql = "select include, description from customcontexts_includes_list where context = '".$context."' order by description";
$sql = "SELECT include, customcontexts_includes_list.description,
COUNT(customcontexts_contexts_list.context) AS preemptcount
FROM customcontexts_includes_list
LEFT OUTER JOIN customcontexts_contexts_list
ON include = customcontexts_contexts_list.context
WHERE customcontexts_includes_list.context = '$context'
ORDER BY customcontexts_includes_list.sort,
customcontexts_includes_list.description";
$results = $db->getAll($sql);
if(DB::IsError($results)) {
$results = array();
}
foreach ($results as $val) {
$tmparray[] = array($val[0], $val[1], $val[2]);
}
return $tmparray;
}
//lists any contexts the user entered in admin for us to parse for includes to make available to his custom contexts
function customcontexts_getcontextslist() {
global $db;
$sql = "select context, description from customcontexts_contexts_list order by description";
$results = $db->getAll($sql);
if(DB::IsError($results)) {
$results = array();
}
foreach ($results as $val) {
$tmparray[] = array($val[0], $val[1]);
}
return $tmparray;
}
//these are the users selections of includes for the selected custom context
function customcontexts_getincludes($context) {
global $db;
$sql = "select customcontexts_contexts_list.context, customcontexts_contexts_list.description as contextdescription, customcontexts_includes_list.include, customcontexts_includes_list.description, if(saved.include is null, 'no', if(saved.timegroupid is null, 'yes', saved.timegroupid)) as allow, saved.sort from customcontexts_contexts_list inner join customcontexts_includes_list on customcontexts_contexts_list.context = customcontexts_includes_list.context left outer join (select * from customcontexts_includes where context = '$context') saved on customcontexts_includes_list.include = saved.include order by customcontexts_contexts_list.description, customcontexts_includes_list.description";
/* $sql = "SELECT customcontexts_contexts_list.context,
customcontexts_contexts_list.description AS contextdescription,
customcontexts_includes_list.include,
customcontexts_includes_list.description,
IF(saved.include is null, 'no',
IF(saved.timegroupid is null, IF(saved.userules is null, 'yes', saved.userules),
saved.timegroupid)) AS allow,
IF(saved.sort is null,customcontexts_includes_list.sort,saved.sort) AS sort,
COUNT(preemptcheck.context) AS preemptcount FROM customcontexts_contexts_list
INNER JOIN customcontexts_includes_list
ON customcontexts_contexts_list.context = customcontexts_includes_list.context
LEFT OUTER JOIN (SELECT * from customcontexts_includes WHERE context = '$context') AS saved
ON customcontexts_includes_list.include = saved.include
LEFT OUTER JOIN customcontexts_contexts_list preemptcheck
ON customcontexts_includes_list.include = preemptcheck.context
LEFT OUTER JOIN outbound_route_sequence
ON REPLACE(customcontexts_includes_list.include,'outrt-','') = outbound_route_sequence.route_id,
customcontexts_contexts_list.description,
customcontexts_includes_list.include,
customcontexts_includes_list.description,
IF(saved.include is null, 'no', IF(saved.timegroupid is null, 'yes', saved.timegroupid)),
saved.sort,
customcontexts_contexts_list.description
ORDER BY
IF(saved.sort is null,201,saved.sort),
customcontexts_includes_list.sort,
outbound_route_sequence.seq,
customcontexts_contexts_list.description,
customcontexts_includes_list.description"; */
$results = sql($sql,'getAll');
foreach ($results as $val){
$tmparray[] = array($val[0], $val[1], $val[2], $val[3], $val[4], $val[5], $val[6]);
}
//0-context 1-contextdescription 2-include 3-description 4-allow 5-sort 6-preemptcount
return $tmparray;
}
//these are the users custom contexts
function customcontexts_getcontexts() {
global $db;
$tmparray = array();
$sql = "select context, description from customcontexts_contexts order by description";
$results = $db->getAll($sql);
if(DB::IsError($results)) {
$results = array();
}
foreach ($results as $val) {
$tmparray[] = array($val[0], $val[1]);
}
return $tmparray;
}
/*
* allow reload to get our config
* we add all user custom contexts ad include his selected includes
* also maybe allow the user to specify invalid destination
*/
function customcontexts_get_config($engine) {
global $ext;
switch($engine) {
case 'asterisk':
global $db;
$sql = "SELECT context, dialrules, faildestination, featurefaildestination,
failpin, featurefailpin FROM customcontexts_contexts";
$results = $db->getAll($sql);
if(DB::IsError($results)) {
$results = array();
}
foreach ($results as $val) {
$context = $val[0];
//$ext->_exts[$context][] = null;
//partially stolen from outbound routing
$dialpattern = explode("\n",$val[1]);
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 (is_array($dialpattern)) {
foreach ($dialpattern 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;
}
$ext->add($context,$pattern, '', new ext_goto('1','${'.$exten.'}',$context.'_rulematch'));
}
}
//switch to first line to deny all access when time group deleted
//$sql = "select include, time from customcontexts_includes left outer join customcontexts_timegroups_detail on customcontexts_includes.timegroupid = customcontexts_timegroups_detail.timegroupid where context = '".$context."' and (customcontexts_includes.timegroupid is null or customcontexts_timegroups_detail.timegroupid is not null) order by sort";
$sql = "SELECT include, time, userules, seq FROM customcontexts_includes
LEFT OUTER JOIN timegroups_details
ON customcontexts_includes.timegroupid = timegroups_details.timegroupid
LEFT OUTER JOIN outbound_route_sequence
ON REPLACE(include,'outrt-','') = outbound_route_sequence.route_id
WHERE context = '$context' ORDER BY sort, seq";
$results2 = $db->getAll($sql);
if(DB::IsError($results2)) {
$results2 = array();
}
foreach ($results2 as $inc) {
$time = isset($inc[1])?'|'.$inc[1]:'';
switch ($inc[2]) {
case 'allowmatch':
if (is_array($dialpattern)) {
$ext->addInclude($context.'_rulematch',$inc[0].$time);
}
break;
case 'denymatch':
$ext->addInclude($context,$inc[0].$time);
break;
default:
$ext->addInclude($context,$inc[0].$time);
if (is_array($dialpattern)) {
$ext->addInclude($context.'_rulematch',$inc[0].$time);
}
break;
}
}
//these go in funny "exten => s,1,Macro(hangupcall,)"
//i'd rather use the base extension class to type it normally, but there is a bug in the class see ticket http://www.freepbx.org/trac/ticket/1453
$ext->add($context,'s', '', new ext_macro('hangupcall'));
$ext->add($context,'h', '', new ext_macro('hangupcall'));
$ext->addInclude($context,$context.'_bad-number');
$ext->addInclude($context,'bad-number');
if (is_array($dialpattern)) {
$ext->add($context.'_rulematch','s', '', new ext_macro('hangupcall'));
$ext->add($context.'_rulematch','h', '', new ext_macro('hangupcall'));
$ext->addInclude($context.'_rulematch',$context.'_bad-number');
$ext->addInclude($context.'_rulematch','bad-number');
}
$ext->_exts[$context.'_bad-number'][] = null;
if (isset($val[2]) && (!$val[2] == '')) {
$goto = explode(',',$val[2]);
if (isset($val[4]) && ($val[4] <> '')) {
$ext->add($context.'_bad-number', '_X.', '', new ext_authenticate($val[4]));
}
$ext->add($context.'_bad-number', '_X.', '', new ext_goto($goto[2],$goto[1],$goto[0]));
}
if (isset($val[3]) && (!$val[3] == '')) {
$goto = explode(',',$val[3]);
if (isset($val[5]) && ($val[5] <> '')) {
$ext->add($context.'_bad-number', '_*.', '', new ext_authenticate($val[5]));
}
$ext->add($context.'_bad-number', '_*.', '', new ext_goto($goto[2],$goto[1],$goto[0]));
}
}
break;
}
}
// returns a associative arrays with keys 'destination' and 'description'
// it allows custom contexts to be chosen as destinations
//this may seem a bit strange, but it works simply it sends the user to the EXTEN he dialed (or IVR option) within the selected context
function customcontexts_destinations() {
$contexts = customcontexts_getcontexts();
$extens[] = array('destination' => 'from-internal,${EXTEN},1', 'description' => 'Full Internal Access');
if (is_array($contexts)) {
foreach ($contexts as $r) {
$extens[] = array('destination' => $r[0].',${EXTEN},1', 'description' => $r[1]);
}
}
return $extens;
}
/*
* admin page helper
* we are using gui styles so there is very little on the page
* the admin page is used to list _existing_ contexts for us to parse for includes
* these contexts/includes can be tagged with a description for the user to select on the custom contexts page
*/
function customcontexts_customcontextsadmin_configpageinit($dispnum) {
global $currentcomponent;
switch ($dispnum) {
case 'customcontextsadmin':
$currentcomponent->addguifunc('customcontexts_customcontextsadmin_configpageload');
$currentcomponent->addprocessfunc('customcontexts_customcontextsadmin_configprocess', 5);
break;
}
}
//this is the dirty work displaying the admin page
function customcontexts_customcontextsadmin_configpageload() {
global $currentcomponent;
$contexterr = 'Context may not be left blank and must contain only letters, numbers and a few select characters!';
$descerr = 'Description must be alpha-numeric, and may not be left blank';
$extdisplay = isset($_REQUEST['extdisplay'])?$_REQUEST['extdisplay']:null;
$action= isset($_REQUEST['action'])?$_REQUEST['action']:null;
if ($action == 'del') {
$currentcomponent->addguielem('_top', new gui_pageheading('title', _("Context").": $extdisplay"." удален!", false), 0);
} else {
//need to get module name/type dynamically
$query = ($_SERVER['QUERY_STRING'])?$_SERVER['QUERY_STRING']:'type=tool&display=customcontextsadmin&extdisplay='.$extdisplay;
$delURL = $_SERVER['PHP_SELF'].'?'.$query.'&action=del';
$info = 'The context which contains includes which you would like to make available to '.customcontexts_getmodulevalue('moduledisplayname').'. Any context you enter here will be parsed for includes and you can then include them in your own '.customcontexts_getmodulevalue('moduledisplayname').'. Removing them here does NOT delete the context, rather makes them unavailable to your '.customcontexts_getmodulevalue('moduledisplayname').'.';
$currentcomponent->addguielem('_top', new gui_hidden('action', ($extdisplay ? 'edit' : 'add')));
$currentcomponent->addguielem('_bottom', new gui_link('help', _(customcontexts_getmodulevalue('moduledisplayname')." v".customcontexts_getmodulevalue('moduleversion')), 'http://www.freepbx.org/support/documentation/module-documentation/third-party-unsupported-modules/customcontexts', true, false), 0);
if (!$extdisplay) {
$currentcomponent->addguielem('_top', new gui_pageheading('title', _("Добавить контекст"), false), 0);
$currentcomponent->addguielem('Context', new gui_textbox('extdisplay', '', 'Контекст', $info, 'isWhitespace() || !isFilename()', $contexterr, false), 3);
$currentcomponent->addguielem('Context', new gui_textbox('description', '', 'Описание', 'This will display as a heading for the available includes on the '.customcontexts_getmodulevalue('moduledisplayname').' page.', '!isAlphanumeric() || isWhitespace()', $descerr, false), 3);
} else {
$savedcontext = customcontexts_customcontextsadmin_get($extdisplay);
$context = $savedcontext[0];
$description = $savedcontext[1];
$locked = $savedcontext[2];
$currentcomponent->addguielem('_top', new gui_hidden('extdisplay', $extdisplay));
$currentcomponent->addguielem('_top', new gui_pageheading('title', _("Edit Context").": $description", false), 0);
if ($locked == false) {
$currentcomponent->addguielem('_top', new gui_link('del', ""._("Remove Context").": $context
", $delURL, true, false), 0);
}
else
{
$currentcomponent->addguielem('_top', new gui_label('del', _("Context").": $context не может быть удален!
", $delURL, true, false), 0);
}
$currentcomponent->addguielem('Context', new gui_textbox('description', $description, 'Описание', 'This will display as a heading for the available includes on the '.customcontexts_getmodulevalue('moduledisplayname').' page.', '!isAlphanumeric() || isWhitespace()', $descerr, false), 3);
$inclist = customcontexts_getincludeslist($extdisplay);
$inclist = is_array($inclist)?$inclist:array();
foreach ($inclist as $val) {
if ($val[2] > 0) {
$currentcomponent->addguielem('Includes Descriptions', new gui_textbox('includes['.$val[0].']', $val[1], ''.$val[0].'', 'This will display as the name of the include on the '.customcontexts_getmodulevalue('moduledisplayname').' page.
NOTE: This include should have a description denoting the fact that allowing it may allow another ENTIRE context!', '!isAlphanumeric() || isWhitespace()', $descerr, false), 3);
} else {
$currentcomponent->addguielem('Includes Descriptions', new gui_textbox('includes['.$val[0].']', $val[1], $val[0], 'This will display as the name of the include on the '.customcontexts_getmodulevalue('moduledisplayname').' page.', '!isAlphanumeric() || isWhitespace()', $descerr, false), 3);
}
}
}
}
}
//handle the admin submit button
function customcontexts_customcontextsadmin_configprocess() {
$action= isset($_REQUEST['action'])?$_REQUEST['action']:null;
$context= isset($_REQUEST['extdisplay'])?$_REQUEST['extdisplay']:null;
$description= isset($_REQUEST['description'])?$_REQUEST['description']:null;
//addslashes
switch ($action) {
case 'add':
customcontexts_customcontextsadmin_add($context,$description);
break;
case 'edit':
customcontexts_customcontextsadmin_edit($context,$description);
$includes = isset($_REQUEST['includes'])?$_REQUEST['includes']:null;
customcontexts_customcontextsadmin_editincludes($context,$includes);
break;
case 'del':
customcontexts_customcontextsadmin_del($context);
break;
}
}
//retrieve a single context for the admin page
function customcontexts_customcontextsadmin_get($context) {
global $db;
$sql = "select context, description, locked from customcontexts_contexts_list where context = '$context'";
$results = $db->getAll($sql);
if(DB::IsError($results)) {
$results = null;
}
$tmparray = array($results[0][0], $results[0][1], $results[0][2]);
return $tmparray;
}
//add a single context for admin
function customcontexts_customcontextsadmin_add($context,$description) {
global $db;
$sql = "insert customcontexts_contexts_list (context, description) VALUES ('$context','$description')";
$db->query($sql);
needreload();
}
//del a single context from admin
function customcontexts_customcontextsadmin_del($context) {
global $db;
$sql = "delete from customcontexts_includes_list where context = '$context'";
$db->query($sql);
$sql = "delete from customcontexts_contexts_list where context = '$context'";
$db->query($sql);
needreload();
}
//update a single context for admin
function customcontexts_customcontextsadmin_edit($context,$description) {
global $db;
$sql = "update customcontexts_contexts_list set description = '$description' where context = '$context'";
$db->query($sql);
needreload();
}
//edit the includes under a single admin context
function customcontexts_customcontextsadmin_editincludes($context,$includes) {
global $db;
$sql = "delete from customcontexts_includes_list where context = '$context'";
$db->query($sql);
$includes = is_array($includes)?$includes:array();
foreach ($includes as $key=>$val) {
$sql = "insert customcontexts_includes_list (context, include, description) values ('$context','$key','$val')";
$db->query($sql);
}
needreload();
}
//---------------------------------------------
/* custom contexts page helper
* we are using gui styles so there is very little on the page
* the custom contexts page is used to create _new_ contexts for use in the dialplan
* these contexts can include any includes which were made available from admin
*/
function customcontexts_customcontexts_configpageinit($dispnum) {
global $currentcomponent;
switch ($dispnum) {
case 'customcontexts':
$currentcomponent->addoptlistitem('includeyn', 'yes', 'Allow');
$currentcomponent->addoptlistitem('includeyn', 'no', 'Deny');
$currentcomponent->addoptlistitem('includeyn', 'allowmatch', 'Allow Rules');
$currentcomponent->addoptlistitem('includeyn', 'denymatch', 'Deny Rules');
$timegroups = timeconditions_timegroups_list_groups();
$timegroups = is_array($timegroups)?$timegroups:array();
foreach ($timegroups as $val) {
$currentcomponent->addoptlistitem('includeyn', $val[0], $val[1]);
}
$currentcomponent->setoptlistopts('includeyn', 'sort', false);
for($i = 0; $i <= 300; $i++) {
$currentcomponent->addoptlistitem('includesort', $i - 50, $i);
}
$currentcomponent->addguifunc('customcontexts_customcontexts_configpageload');
$currentcomponent->addprocessfunc('customcontexts_customcontexts_configprocess', 5);
break;
}
}
//actually render the custom contexts page
function customcontexts_customcontexts_configpageload() {
global $currentcomponent;
$contexterr = 'Context may not be left blank and must contain only letters, numbers and a few select characters!';
$descerr = 'Description must be alpha-numeric, and may not be left blank';
$extdisplay = isset($_REQUEST['extdisplay'])?$_REQUEST['extdisplay']:null;
$action= isset($_REQUEST['action'])?$_REQUEST['action']:null;
$showsort = isset($_REQUEST['showsort'])?$_REQUEST['showsort']:null;
if (isset($showsort) && $showsort <> customcontexts_getmodulevalue('displaysortforincludes')) {
customcontexts_setmodulevalue('displaysortforincludes', $showsort);
}
if ($action == 'del') {
$currentcomponent->addguielem('_top', new gui_pageheading('title', _("Context").": $extdisplay"." удален!", false), 0);
} else {
//need to get page name/type dynamically
//caused trouble on dup or del after dup or rename
//$query = ($_SERVER['QUERY_STRING'])?$_SERVER['QUERY_STRING']:'type=setup&display=customcontexts&extdisplay='.$extdisplay;
$query = 'type=setup&display=customcontexts&extdisplay='.$extdisplay;
$delURL = $_SERVER['PHP_SELF'].'?'.$query.'&action=del';
$dupURL = $_SERVER['PHP_SELF'].'?'.$query.'&action=dup';
$info = 'The custom context to make will be available in your dialplan. These contexts can be used as a context for a device/extension to allow them limited access to your dialplan.';
$currentcomponent->addguielem('_bottom', new gui_link('ver', _(customcontexts_getmodulevalue('moduledisplayname')." v".customcontexts_getmodulevalue('moduleversion')), 'http://www.freepbx.org/support/documentation/module-documentation/third-party-unsupported-modules/customcontexts', true, false), 0);
if (!$extdisplay) {
$currentcomponent->addguielem('_top', new gui_pageheading('title', _("Добавить контекст"), false), 0);
$currentcomponent->addguielem('Context', new gui_textbox('extdisplay', '', 'Контекст', $info, 'isWhitespace() || !isFilename()', $contexterr, false), 3);
$currentcomponent->addguielem('Context', new gui_textbox('description', '', 'Описание', 'This will display as the name of this custom context.', '!isAlphanumeric() || isWhitespace()', $descerr, false), 3);
} else {
$savedcontext = customcontexts_customcontexts_get($extdisplay);
$context = $savedcontext[0];
$description = $savedcontext[1];
$rulestext = $savedcontext[2];
$faildest = $savedcontext[3];
$featurefaildest = $savedcontext[4];
$failpin = $savedcontext[5];
$featurefailpin = $savedcontext[6];
$currentcomponent->addguielem('_top', new gui_hidden('extdisplay', $extdisplay));
$currentcomponent->addguielem('_top', new gui_pageheading('title', _("Edit Context").": $description", false), 0);
//$currentcomponent->addguielem('_top', new gui_link('del', _("Delete Context")." $context", $delURL, true, false), 0);
$currentcomponent->addguielem('_top', new guielement('del', '