Added Vodia Identity Switcher for Xinon Users

This commit is contained in:
Frank Schubert
2025-06-24 11:32:53 +02:00
parent 2fbba259ed
commit 02a423d300
9 changed files with 508 additions and 17 deletions

View File

@@ -103,20 +103,44 @@ $siteTitle = "Benutzer";
</option>
</select>
</div>
<div class="form-group <?=(!isset($user) || !$user->is("employee")) ? "hidden" : ""?>"
id="employee-number-container">
<label for="employee_number"><?=TT_SYSOWNER_NAME_HTML?> Mitarbeiternummer:</label>
<input type="text" id="employee_number" name="employee_number" class="form-control"
value="<?=(isset($user)) ? (new WorkerFlag($user->id, "employee_number"))->value() : ""?>"/>
</div>
<div class="form-group <?=(!isset($user) || !$user->is("employee")) ? "hidden" : ""?>"
id="project-api-key-container">
<label for="project_api_key">OpenProject API Key:</label>
<input type="text" id="project_api_key" name="project_api_key" class="form-control"
value="<?=(isset($user)) ? (new WorkerFlag($user->id, "project_api_key"))->value() : ""?>"/>
<div id="employee-container" <?=(!isset($user) || !$user->is("employee")) ? "hidden" : ""?>>
<div class="form-group">
<label for="employee_number"><?=TT_SYSOWNER_NAME_HTML?> Mitarbeiternummer:</label>
<input type="text" id="employee_number" name="employee_number" class="form-control"
value="<?=(isset($user)) ? (new WorkerFlag($user->id, "employee_number"))->value() : ""?>" />
</div>
<div class="form-group">
<label for="employee_number">Vodia Outbound Identity - Domain:</label>
<input type="text" id="vodia_identity_domain" name="vodia_identity_domain" class="form-control"
value="<?=(isset($user)) ? (new WorkerFlag($user->id, "vodia_identity_domain"))->value() : ""?>" />
</div>
<div class="form-group">
<label for="employee_number">Vodia Outbound Identity - Username (Extension):</label>
<input type="text" id="vodia_identity_username" name="vodia_identity_username" class="form-control"
value="<?=(isset($user)) ? (new WorkerFlag($user->id, "vodia_identity_username"))->value() : ""?>" />
</div>
<div class="form-group">
<label for="employee_number">Vodia Outbound Identity - Standard Identität:</label>
<input type="text" id="vodia_identity_default" name="vodia_identity_default" class="form-control"
value="<?=(isset($user)) ? (new WorkerFlag($user->id, "vodia_identity_default"))->value() : ""?>" />
<small>+43 720 123456</small>
</div>
<div class="form-group">
<label for="project_api_key">OpenProject API Key:</label>
<input type="text" id="project_api_key" name="project_api_key" class="form-control"
value="<?=(isset($user)) ? (new WorkerFlag($user->id, "project_api_key"))->value() : ""?>" />
</div>
</div>
<hr />
<div class="form-group">
<label for="password">Password:</label>
<input type="password" id="password" name="password" class="form-control" value=""/>
@@ -557,9 +581,9 @@ $siteTitle = "Benutzer";
$("#employee").change(function () {
if ($("#employee").val() == "true") {
$("#employee-number-container").show(400);
$("#employee-container").show(400);
} else {
$("#employee-number-container").hide(400);
$("#employee-container").hide(400);
}
});
});

View File

@@ -16,6 +16,11 @@
<!-- end Footer -->
<!--<script type="text/javascript" src="<?=self::getResourcePath()?>assets/js/vendor.min.js"></script>-->
<?php if(defined("ENABLE_VODIA_IDENTITY_SWITCHER") && ENABLE_VODIA_IDENTITY_SWITCHER && $me->is("employee")): ?>
<script type="text/javascript" src="<?=self::getResourcePath()?>assets/js/xinon-vodia-identity.js"></script>
<?php endif; ?>
<script type="text/javascript">
$(".selectpicker").selectpicker({
iconBase: "fas",
@@ -36,7 +41,7 @@
$(this).parent('li').toggleClass('open').find('.submenu:first').toggleClass('open');
}
});
<?php if($me->can("Superexpert")): ?>
$(".secondO").addClass("pointer");

View File

@@ -59,6 +59,8 @@
<script type="text/javascript" src="<?=self::getResourcePath()?>plugins/bookstack/bookstackIntegration.js"></script>
<script type="text/javascript" src="<?=self::getResourcePath()?>assets/libs/switchery/switchery.min.js"></script>
<script type="text/javascript">
window.mfNotify = <?=isset($mfNotify) ? json_encode($mfNotify) : "null"; ?>;
window.TT_CONFIG = {};
@@ -93,9 +95,11 @@
<body>
<script type="text/javascript">
baseurl = '<?=self::getResourcePath()?>';
var baseurl = '<?=self::getResourcePath()?>';
</script>
<!-- Navigation Bar-->
<header id="topnav">
<?php include(realpath(dirname(__FILE__)."/")."/topbar.php"); ?>

View File

@@ -15,6 +15,43 @@
<!-- End mobile menu toggle-->
</li>
<li id="vodia-identity-container" class="dropdown notification-list hidden" >
<a href="#" id="vodia-identity-switcher" class="nav-link nav-user dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
<i class="phone-icon fas fa-phone text-success"></i>
<span class="pro-user-name ml-1">
Ausgehende Identität: <strong id="vodia-identity-current-text"></strong>
<strong id="vodia-identity-current-number"></strong>
<i class="far fa-chevron-down"></i>
</span>
</a>
<div id="vodia-identity-switcher-dropdown-menu" class="dropdown-menu dropdown-menu-right">
<div class="dropdown-item noti-title">
<h6 class="m-0">
Ausgehende Telefonidentität umschalten:
</h6>
</div>
<ul class="list-group" id="vodia-identities-list" style="min-width: 512px;">
</ul>
</div>
</li>
<template id="vodia-identity-list-item-template">
<li class="list-group-item pointer">
<table class="table table-borderless mb-0">
<tr>
<td class="color-block"><i class="fas fa-phone"></i></td>
<td class="text-block" style="vertical-align: middle">
<span class="name"></span>
<span class="number"></span>
</td>
</tr>
</table>
</li>
</template>
<li id="bookstackLink" class="dropdown notification-list" style="display: none">
<a class="nav-link dropdown-toggle" href="" target="_blank">
<i class="fas fa-book"></i>

View File

@@ -313,6 +313,31 @@ class UserController extends mfBaseController
$pak->delete();
}
// vodia identity data
$vid = new WorkerFlag($user->id, "vodia_identity_domain");
if($r->vodia_identity_domain) {
$vid->value($r->vodia_identity_domain);
$vid->save();
} else {
$vid->delete();
}
$viu = new WorkerFlag($user->id, "vodia_identity_username");
if($r->vodia_identity_username) {
$viu->value($r->vodia_identity_username);
$viu->save();
} else {
$viu->delete();
}
$vdi = new WorkerFlag($user->id, "vodia_identity_default");
if($r->vodia_identity_default) {
$vdi->value($r->vodia_identity_default);
$vdi->save();
} else {
$vdi->delete();
}
}
@@ -406,6 +431,12 @@ class UserController extends mfBaseController
case "endse":
$return = $this->endSuperexpertApi();
break;
case "getVodiaIdentity":
$return = $this->getVodiaIdentityApi();
break;
case "setVodiaIdentity":
$return = $this->setVodiaIdentityApi();
break;
default:
$return = false;
}
@@ -419,6 +450,74 @@ class UserController extends mfBaseController
$this->returnJson($data);
}
private function getVodiaIdentityApi() {
if(!ENABLE_VODIA_IDENTITY_SWITCHER) {
return ["enabled" => false];
}
$vodia = new Vodia_Api(VODIA_API_URL, VODIA_API_ADMIN_USER, VODIA_API_ADMIN_PASS);
$domain = $this->me->getFlag("vodia_identity_domain")->value();
$username = $this->me->getFlag("vodia_identity_username")->value();
$default = $this->me->getFlag("vodia_identity_default")->value();
if(!$domain || !$username || !$default) {
return ["enabled" => false];
}
$current = $vodia->getUsersetting($domain, $username, "ani");
if($current) {
if(str_replace(" ", "", $current) == str_replace(" ", "", $default)) {
$current = $default;
}
} else {
$current = $default;
}
return [
"enabled" => true,
"domain" => $domain,
"username" => $username,
"default" => $default,
"default_number" => str_replace(" ", "", $default),
"current" => $current,
"identities" => VODIA_OUTBOUND_IDENTITIES,
];
}
private function setVodiaIdentityApi() {
if(!ENABLE_VODIA_IDENTITY_SWITCHER) {
return ["enabled" => false];
}
$number = $this->request->number;
if(!$number) {
return false;
}
// expects number to start with +
if(!substr($number, 0, 1) == "+") {
return false;
}
$domain = $this->me->getFlag("vodia_identity_domain")->value();
$username = $this->me->getFlag("vodia_identity_username")->value();
if(!$domain || !$username) {
return ["enabled" => false];
}
$vodia = new Vodia_Api(VODIA_API_URL, VODIA_API_ADMIN_USER, VODIA_API_ADMIN_PASS);
if(!$vodia->setUsersettings($domain, $username, ["ani" => $number])) {
return false;
}
return ["enabled" => true, "number" => $number];
}
private function startSuperexpertApi() {
$me = new User();
$me->loadMe();

View File

@@ -43,12 +43,12 @@ class WorkerFlag {
if($id) {
// update
$this->db->update("WorkerFlag", $values, "id=$id");
$this->db->update("WorkerFlag", $values, "id=$id", ["value"]);
} else {
// insert
$values['create_by'] = $this->user->id;
$values['create'] = date('U');
$id = $this->db->insert("WorkerFlag", $values);
$id = $this->db->insert("WorkerFlag", $values, ["value"]);
$this->id = $id;
}

148
lib/Vodia/Api.php Normal file
View File

@@ -0,0 +1,148 @@
<?php
class Vodia_Api {
private $log;
private $baseurl;
private $admin_user;
private $admin_pass;
private $session_name;
private $session_id;
public function __construct($baseurl, $admin_user, $admin_pass) {
$this->log = mfLoghandler::singleton();
$this->baseurl = rtrim($baseurl, '/');
$this->admin_user = $admin_user;
$this->admin_pass = $admin_pass;
if(!$this->baseurl || !$this->admin_user || !$this->admin_pass) {
throw new Exception("Invalid Arguments");
}
}
private function _authenticate($domain) {
$url = $this->baseurl.VODIA_API_EP_SYSTEM_SESSION;
$session_id = false;
//$this->session_name = sprintf("%x", crc32(uniqid("thetool", true)));
$this->session_name = "auth";
$ctx_options = ["http" => [
"ignore_errors" => true,
"method" => "POST",
"headers" => [
"Accept: application/json",
"Content-Type: application/json",
],
"content" => json_encode([
"name" => $this->session_name,
"value" => "$this->admin_user ".md5($this->admin_pass),
"admin" => true,
"domain" => $domain,
]),
//"header" => $headers,
]];
$this->log->debug(__METHOD__.": authenticating to $url for domain $domain, user $this->admin_user");
$this->log->debug(__METHOD__.": ".print_r($ctx_options, true));
$ctx = stream_context_create($ctx_options);
$output = file_get_contents($url, false, $ctx);
$this->log->debug(__METHOD__.": auth output: $output");
$m = [];
if(preg_match('/^"([^"]+)"$/', $output, $m)) {
if($m[1]) {
$session_id = $m[1];
}
}
if(!$session_id) {
throw new Exception("Authentication failed for domain $domain");
}
$this->session_id = $session_id;
return true;
}
public function setUsersettings($domain, $user, Array $user_settings) {
if(!$this->session_id) {
$this->_authenticate($domain);
}
$url = $this->baseurl.VODIA_API_EP_POST_USER_SETTINGS;
$url = str_replace("{DOMAIN}", $domain, $url);
$url = str_replace("{EXT}", $user, $url);
$ctx_options = [
"http" => [
"ignore_errors" => true,
"method" => "POST",
"header" => [
"Cookie: session=".$this->session_id,
"Accept: application/json",
"Content-Type: application/json",
],
"content" => json_encode($user_settings),
]
];
$ctx = stream_context_create($ctx_options);
$output = file_get_contents($url, false, $ctx);
$this->log->debug(__METHOD__.": output: $output");
return true;
}
/**
* Get user settings for a specific user in a domain.
*
* @param string $domain The domain to query.
* @param string $user The user extension to query.
* @param string|bool $key Optional. If provided, only the value for this key will be returned.
* @return mixed Returns the user settings as an associative Array or a specific key's value as string if $key is provided or false if $key is not available.
*/
public function getUsersetting($domain, $user, $key = false) {
if(!$this->session_id) {
$this->_authenticate($domain);
}
$url = $this->baseurl.VODIA_API_EP_GET_USER_SETTINGS;
$url = str_replace("{DOMAIN}", $domain, $url);
$url = str_replace("{EXT}", $user, $url);
$ctx_options = [
"http" => [
"ignore_errors" => true,
"method" => "GET",
"header" => [
"Cookie: session=".$this->session_id,
"Accept: application/json",
"Content-Type: application/json",
],
]
];
$ctx = stream_context_create($ctx_options);
$output = file_get_contents($url, false, $ctx);
$this->log->debug(__METHOD__.": output: $output");
$user_settings = json_decode($output, true);
if($key) {
if(array_key_exists($key, $user_settings)) {
return $user_settings[$key];
} else {
return false;
}
}
return $user_settings;
}
}

View File

@@ -133,6 +133,33 @@ h1, h2, h3, h4, h5, h6 {
border-bottom: 2px solid #f1556c;
}*/
#vodia-identities-list .list-group-item:hover {
background-color: #dee2e6;
}
#vodia-identities-list .list-group-item td.color-block {
width: 50px;
height: 16px;
color: #fff;
vertical-align: top;
text-align: center;
border: 1px solid #fff;
}
#vodia-identities-list .list-group-item td.color-block-blue {
background-color: #4b88e4;
}
#vodia-identities-list .list-group-item td.color-block-green {
background-color: #25b343;
}
#vodia-identities-list .list-group-item td.color-block-pink {
background-color: #f672a7;
}
#vodia-identities-list .list-group-item td.color-block-black {
background-color: #000;
}
#topnav {
box-shadow: 2px 2px 1px #a0a0a0;
}

View File

@@ -0,0 +1,147 @@
/* Vodia Outbound Identity */
var vodia_identity = null;
getVodiaIdentity();
async function getVodiaIdentity() {
if(!$("#vodia-identity-switcher .phone-icon").hasClass("fa-spin")) {
$("#vodia-identity-switcher .phone-icon").addClass("fa-spin text-warning");
}
var response = await fetch(baseurl + '/User/Api/do=getVodiaIdentity');
if (!response.ok) {
$("#vodia-identity-switcher a.phone-icon i").removeClass("fa-spin");
return false;
}
var resp_json = await response.json();
if(resp_json.status != "OK") {
console.error("Error getting Vodia Identity: " + resp_json.message);
$("#vodia-identity-switcher a.phone-icon i").removeClass("fa-spin");
return false;
}
vodia_identity = resp_json.result;
if(!("enabled" in vodia_identity) || !vodia_identity.enabled) {
$("#vodia-identity-switcher a.phone-icon i").removeClass("fa-spin");
return true;
}
var default_ident = vodia_identity.default;
var default_ident_number = vodia_identity.default_number;
var current_ident = vodia_identity.current;
var current_ident_number = current_ident.replace(" ", "");
var identities = vodia_identity.identities;
$("#vodia-identity-container").show();
if(default_ident_number == current_ident_number) {
$("#vodia-identity-current-text").text("Standard");
$("#vodia-identity-current-number").text("(" + default_ident + ")");
$("#vodia-identity-switcher").attr("title", "Standard (" + default_ident + ")");
} else {
ident_found = false;
for(const [ident_name, ident] of Object.entries(identities)) {
if(ident.number == current_ident_number) {
ident_found = true;
$("#vodia-identity-current-text").text(ident_name);
$("#vodia-identity-current-number").text("(" + ident.display + ")");
$("#vodia-identity-switcher").attr("title", ident_name + " (" + ident.display + ")");
}
}
if(!ident_found) {
$("#vodia-identity-current-number").text(vodia_identity.current);
$("#vodia-identity-switcher").attr("title", vodia_identity.current);
}
}
$("#vodia-identities-list").empty();
let item = addVodiaIdentityListItem(
default_ident_number,
default_ident_number,
"Xinon Eigene Durchwahl",
"blue"
);
if(default_ident_number == current_ident_number) {
$(item).addClass("bg-info text-white");
$(item).find(".text-block").addClass("text-white");
$(item).removeClass("pointer");
} else {
$(item).click(function () {
setVodiaOutboundIdentity(default_ident_number)
});
}
for(const [ident_name, ident] of Object.entries(identities)) {
let item = addVodiaIdentityListItem(
ident.number,
ident.display,
ident_name,
ident.color
);
if(ident.number == current_ident_number) {
$(item).addClass("bg-info text-white");
$(item).find(".text-block").addClass("text-white");
$(item).removeClass("pointer");
} else {
$(item).click(function () {
setVodiaOutboundIdentity(ident.number)
});
}
}
$("#vodia-identity-switcher .phone-icon").removeClass("text-warning fa-spin");
$("#vodia-identity-switcher .phone-icon").addClass("fa-bounce");
setTimeout(() => { $("#vodia-identity-switcher .phone-icon").removeClass("fa-bounce"); }, 800);
}
function addVodiaIdentityListItem(number, number_display, name, color) {
let tpl = $("#vodia-identity-list-item-template")[0].innerHTML;
item = $($.parseXML(tpl)).contents();
$(item).data("number", number);
$(item).find("td.color-block").addClass("color-block-" + color);
$(item).find("span.name").text(name);
$(item).find("span.number").text("(" + number_display + ")");
$("#vodia-identities-list").append(item);
return item;
}
async function setVodiaOutboundIdentity(number) {
$("#vodia-identity-switcher .phone-icon").addClass("fa-spin text-warning");
$("#vodia-identities-list").empty();
$("#vodia-identities-list").append("<img src='" + baseurl + "img/ajax-loader.gif' />");
var response = await fetch(baseurl + '/User/Api/do=setVodiaIdentity',{
method: 'POST',
body: new URLSearchParams({
'number': number
})
});
if (!response.ok) {
return false;
}
var resp_json = await response.json();
if(resp_json.status != "OK") {
console.error("Error setting Vodia Identity: " + resp_json.message);
notify.error("Fehler beim Setzen der Identität!");
}
getVodiaIdentity();
return true;
}