Skip to content

Instantly share code, notes, and snippets.

@gnarfel
Created October 6, 2015 04:18
Show Gist options
  • Save gnarfel/71c6f39718090eabe8e5 to your computer and use it in GitHub Desktop.
Save gnarfel/71c6f39718090eabe8e5 to your computer and use it in GitHub Desktop.
A compact web app to handle the Services button on Cisco IP Phones attached to a CUCM.
<?php
/*
Anthony Fiumara
afiumara@syrdiocese.org
Written by Anthony Fiumara for the Roman Catholic Diocese of Syracuse, September 2015
Requires NO database or flat file system, all config is performed by editing the code
directly in the settings and menu definition sections. The syntax is basically nested
PHP arrays for most of the settings. If you're uncomfortable with this concept, I'd
suggest you Google it. Otherwise just carefully modify the original file (keeping the
periods, commas, semicolons, equals signs and any other punctuation intact as best
as possible.) An extra comma is ok at the end of a line inside an array definition.
If you'd prefer to read config from files (people, groups, menu permissions, menu
definitions, etc) this script is heavily commented and you really only need to
populate the correct arrays. You could remove the array definitions below and populate
them yourself from files or your DB of choice.
** INSTALLATION **
Install any webserver that can run vanilla PHP scripts. This doesn't use ANYTHING that
the default config of PHP does not support.
In CUCM configure the URL to this script in TWO places.
1) System->Enterprise Parameters *This is for older phones that do not support the
services 'menu', only the services 'button'.
2) Device->Phone Settings->Phone Services
Create a new Service with whatever name and title you'd like.
In the URL, make sure it ends with ?name=#DEVICENAME#
It should look like this:
http://ip.address.or.host.name/folder_of_scripts/this_script.php?name=#DEVICENAME#
That's it! Configure below. Read the comments.
Todo list:
Support the querystringparameter: URI for SoftKeys and MenuItems
Handle the <title> detection better, possibly using the UserAgent
Hide menuitems and softkeys that the user does not have access to.
Honestly the whole thing would be better with some simple AD integration
Notes on that: You could set a default in config, then just check for
groups with a specific prefix like PhoneMenu-MenuName
*/
//SETTINGS
//External Apps Settings
//An external app is a URL that can display Cisco XML. The phone itself is directed
// there, no translation is done.
//Example:
// $external_apps['app_name'] = "http://external.app.url/";
//
// Then use it in $menuitems:
// $menuitems = array(
// "External App Item" => ExternalApp('app_name'),
// );
$external_apps = array();
$external_apps['timeclock'] = "http://timeclock/timeclock.php";
$external_apps['salesquota'] = "http://salesquota/display.php";
//Roles and permissions.
//Because the app only has a MAC address to work with and this script is too simple
// to have a connection to the CUCM, here is a simple way to control menu permissions.
//It's built like a heirarchy. People belong to Groups, and Groups have permissions to
// access menus.
//If you'd like to disable the permissions entirely, set the following line to
// false instead of true.
$perms_enabled = true;
//People Definitions
//It really should have been called Devices.
// Example: "jdoe" => "SEPFEEDDEADBEEF"
// Or: "rm232" => "SEPABCDEFABCDEF"
// ^^ SEP + MAC Address in upper case is what Cisco IP phones will
// insert in place of #DEVICENAME# in the Services URL inside
// CUCM.
// Fun fact: SEP stands for Selsius Ethernet Phone, a company
// Cisco acquired in the late 90s that used tftp for their
// device configs.
$people = array(
"jdoe" => "SEPC8F9F968EAFC",
"btucker" => "SEPC8F9F969FEC5",
"rfeldspar" => "SEP8734655146BE",
"mhoncho" => "SEP2938498ACEF2",
"dconnor" => "SEP00DEADBEEF00",
);
//Group Definitions
//Add people to groups. Groups can be nested exactly once using g:groupname instead
// of a person name. Role-style groups can be mixed with users and g:groups
// Example
/*
"mygroup" => array(
"jdoe",
"bsmith",
),
*/
$groups = array(
"it" => array(
"mhoncho",
"dconnor",
),
"reception" => array(
"jdoe",
),
"sales" => array(
"btucker",
"rfeldspar",
),
"can_page" => array(
"g:it",
"g:reception",
),
"can_view_salesquota" => array(
"g:it",
"g:sales",
),
);
//Menu Permissions settings
//Apply a rudimentary set of permissions to menu objects based on group membership.
//There is a special group called all that allows any phone to access this resource.
//There is a special group called none that disallows access from any phone (Block).
//--vv--READ THIS--vv--
//There is no nesting here and a 1:1 relationship between groups and menu_permissions
// objects. This means you can only define ONE 'group' allowed to view a menu.
//It's best to define groups of users AND groups representing roles like
// 'can_use_intercom', then nest the user groups inside of the role groups.
//--^^--READ THIS--^^--
//If there is no definition here, the default is none (if $perms_enabled is true).
//The group names are defined above in Group Settings.
$menu_permissions = array(
"main" => "all", //It's probably best to have "main" => "all", unless you really
// want to restrict the main menu to only certain phones.
"timeclock" => "all", //All devices are allowed to use the Timeclock custom app.
"salesquota" => "can_view_salesquota",
"intercom" => "can_page",
);
//END SETTINGS
//Don't edit this part!
header("Content-type: text/xml\n\n");
$menu = $_REQUEST['menu'];
if ($menu == "") { $menu = "main"; }
$phone = $_REQUEST['name'];
$person = "";
foreach ($people as $fName=>$fPhone) { if ($fPhone == $phone) { $person = $fName; } }
CheckAuth();
switch ($menu) {
//End Don't edit this part!
//Menu Definitions
//READ THIS
//Default/no menu must be last for catch all matching reasons (has to be the last
// thing to be processed.)
//--Intercom Menu--
case "intercom":
$menuitems = array(
"Page ALL" => "Dial:880",
"Page BuildingA" => "Dial:882",
"Page BuildingB" => "Dial:883",
"Page BuildingC" => "Dial:881",
);
$extrasoftkeys = array(
"Page ALL" => "Dial:880",
);
ShowMenuScreen(
$menuitems,
"Intercom Paging",
"Choose a group to page.",
$extrasoftkeys
);
break;
//End --Intercom Menu--
//--SalesQuota Menu--
//Required to show single item menu because permissions dont apply to ExternalApp()
//There's actually a better way to do this. The phones support Location: headers with
// 301/etc messages too.
case "salesquota":
$menuitems = array(
"Sales Quotas" => ExternalApp('salesquota'),
);
$extrasoftkeys = array(
"Sales Quotas" => ExternalApp('salesquota'),
);
ShowMenuScreen(
$menuitems,
"Sales Quotas",
"Display the current Sales Quotas.",
$extrasoftkeys
);
break;
//End --Salesquota Menu--
//--Timeclock Menu--
case "timeclock":
$inputitems = array(
"Employee ID" => array(
"querystringparam"=>"employeeid",
"defaultvalue"=>"",
"inputflags"=>"N" //Number only mask
),
"PIN" => array(
"querystringparam"=>"pin",
"defaultvalue"=>"",
"inputflags"=>"PN" //Password (***) and number mask together.
),
);
ShowInputScreen(
$inputitems,
"Timeclock",
"Enter your Employee ID and PIN",
ExternalApp("timeclock") // will generate a url like
// http://timeclock/timeclock.php?employeeid=1234&pin=5678
// GET requests always! Phone does not POST!
);
break;
//End --Timeclock Menu--
//--Default Menu--
case "main":
case "":
default:
$menuitems = array(
"Timeclock" => MenuURL("timeclock"),
"Intercom Paging" => MenuURL("intercom"),
"Sales Quotas" => MenuURL("salesquota"),
);
$extrasoftkeys = array(
);
//Due to a bug in the 7942 (and possibly others) you can't send a <title>
// for the default service. The last paramter (true in this call, false/not set
// in all other calls) disables sending the <title/> xml tag for just the default
// menu.
ShowMenuScreen(
$menuitems,
"Roman Catholic Diocese of Syracuse",
"Welcome $person select a service.",
$extrasoftkeys,
true
);
break;
//End --Default Menu--
}
//Start Functios, aka don't change the rest unless you're familiar with it
//it's pretty simple, echo the static parts right from the function and foreach echo the dynamic parts
//using their own function to generate them. the PHP jit compiler will inline them anyways.
function ExternalApp($app) {
//This function is very simple.
global $external_apps;
return $external_apps[$app];
}
function MenuURL($menuName) {
global $phone;
//Ideally this function will return http://server/url?name=$name&menu=$menuName.
//Only supports http and port 80 as it stands now.
return "http://" . $_SERVER['SERVER_NAME'] . $_SERVER['SCRIPT_NAME'] . "?name=$phone&amp;menu=".$menuName;
}
//Display 'screen' functions (called from switch(){} above after a menu/softkeys are defined in arrays and passed
// in as paramters.)
function ShowInputScreen($inputs,$title,$prompt,$URL,$extrasoftkeys=array()) {
echo "<CiscoIPPhoneInput><Title>$title</Title><Prompt>$prompt</Prompt><URL>$URL</URL>\n";
foreach ($inputs as $text=>$params) {
echo "<InputItem><DisplayName>$text</DisplayName><QueryStringParam>{$params[querystringparam]}</QueryStringParam><DefaultValue>{$params[defaultvalue]}</DefaultValue><InputFlags>{$params[inputflags]}</InputFlags></InputItem>\n";
}
if (count($extrasoftkeys) > 0) { //only run this if we specified extra soft keys
//nifty trick: if we start at 2, add 2 at the end of the loop, and check for 4 at the beginning
//we can get away with only allowing two softkeys and skipping 1 (select) and 3 (cancel) so we
//can hardcode them. plus $i is set for the <Position> element, no maths. neato++
$i = 2;
foreach ($extrasoftkeys as $text=>$URL) {
if ($i == 4) {
break;
}
SoftKey($text, $URL, $i);
$i++;
$i++;
}
}
echo "</CiscoIPPhoneInput>\n";
}
function ShowMenuScreen($menuitems,$title,$prompt,$extrasoftkeys=array(),$isdefault=false) {
echo "<CiscoIPPhoneMenu><Prompt>$prompt</Prompt>\n";
if ($isdefault == false) {
//This is to correct a bug in the 7942 and possibly others. If you send
echo "<title>$title</title>\n";
}
foreach ($menuitems as $text=>$URL) {
MenuItem($text, $URL);
}
if (count($extrasoftkeys) > 0) { //only run this if we specified extra soft keys
//nifty trick: if we start at 2, add 2 at the end of the loop, and check for 4 at the beginning
//we can get away with only allowing two softkeys and skipping 1 (select) and 3 (cancel) so we
//can hardcode them. plus $i is set for the <Position> element, no maths. neato++
$i = 2;
foreach ($extrasoftkeys as $text=>$URL) {
if ($i == 4) {
break;
}
SoftKey($text, $URL, $i);
$i++;
$i++;
}
}
SoftKey("Select","SoftKey:Select",1);
SoftKey("Cancel","SoftKey:Exit",3);
echo "</CiscoIPPhoneMenu>\n";
}
function ShowTextScreen($text,$title,$prompt,$extrasoftkeys=array()) {
echo "<CiscoIPPhoneText><Title>$title</Title><Text>$text</Text><Prompt>$prompt</Prompt>\n";
if (count($extrasoftkeys) > 0) { //only run this if we specified extra soft keys
//nifty trick: if we start at 2, add 2 at the end of the loop, and check for 4 at the beginning
//we can get away with only allowing two softkeys and skipping 1 (select) and 3 (cancel) so we
//can hardcode them. plus $i is set for the <Position> element, no maths. neato++
$i = 2;
foreach ($extrasoftkeys as $text=>$URL) {
if ($i == 4) {
break;
}
SoftKey($text, $URL, $i);
$i++;
$i++;
}
}
SoftKey("Exit","SoftKey:Exit",3);
echo "</CiscoIPPhoneText>\n";
}
//Easy helper functions to generate the <MenuItem/> and <SoftKey/> tags.
function MenuItem($miName, $miURL) {
echo "<MenuItem><Name>$miName</Name><URL>$miURL</URL></MenuItem>\n";
}
function SoftKey($skName, $skURL, $skPosition) {
echo "<SoftKeyItem><Name>$skName</Name><URL>$skURL</URL><Position>$skPosition</Position></SoftKeyItem>\n";
}
//Permissions function intended to be run right after case "menuName": statement.
//Expects to be able to use a bunch of globals, so if you change the meat and potatoes of this
// program check in over here occasionally. Bad code flow means this will abort the whole script.
function CheckAuth () {
global $perms_enabled, $people, $groups, $menu_permissions,$menu,$phone,$person;
if ($perms_enabled == true) {
if ($menu_permissions[$menu] == "all") {
return true;
}
if ($menu_permissions[$menu] == "none") {
ShowError();
}
if (!isset($menu_permissions[$menu])) { ShowError(); }
$ThisGroupRole = $menu_permissions[$menu];
$ValidUser = false;
if (in_array($person,$groups[$ThisGroupRole])) {
$ValidUser = true;
}
foreach ($groups[$ThisGroupRole] as $GroupUser) {
if (substr($GroupUser,0,2) == "g:") {
$SubGroupRole = substr($GroupUser,2);
if (in_array($person,$groups[$SubGroupRole])) {
$ValidUser = true;
}
}
}
if ($ValidUser == false) {
ShowError();
}
}
}
//Generates a simple text window to show an error
function ShowError() {
ShowTextScreen("This device is not authorized to perform that function.","Not Authorized","Authorization Error");
exit;
}
?>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment