Skip to content

Commit

Permalink
Support fritzco phone books in call.php
Browse files Browse the repository at this point in the history
Fix call.php form submit (reserved function name)

Add genkey.php & outsource to gen_key() in shared.php
Merge duplicate code in index.php
Add TODO & BUG comments
  • Loading branch information
jyevon committed Dec 17, 2022
1 parent c02d446 commit 4b655c6
Show file tree
Hide file tree
Showing 9 changed files with 168 additions and 85 deletions.
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ located in [`cisco/`](cisco/)
1. Clone or download this repository and put the files on a web server that supports PHP (tested on apache2 with libapache2-mod-php and libapache2-mod-fcgid).
2. [Register an app for OAuth 2.0 in the Microsoft Azure app registration portal](https://docs.microsoft.com/en-us/graph/auth-v2-user#1-register-your-app).
3. There, add the URL of the [`oauth-grant.php`](oauth-grant.php) on your web server as a redirect URI. For example, `https://example.com/ipphones/oauth-grant.php`.
4. Rename [`config.example.php`](includes/config.example.php) in [`includes`](includes/) to `config.php` and replace the default values with your Client ID and Client Secret.
4. Rename or copy [`config.example.php`](includes/config.example.php) in [`includes`](includes/) to `config.php` and replace the default values with your Client ID and Client Secret.
5. Also, add your IP Phones if you want to use [`call.php`](#browser-endpoints) or [`cisco/authenticate.php`](#endpoints-for-cisco-ip-phones).
- [`authenticate.php`](cisco/authenticate.php) requires `devicename`, `username` and `password` per phone,
- [`call.php`](#notes-on-callphpcallphp) uses `label`, `devicename` (and optionally `host`)
Expand All @@ -35,16 +35,20 @@ located in [`cisco/`](cisco/)
(e. g. `https://example.org/ipphones/cisco/directory.php?key=`f53fe305ad73b3ff33cf)

# Notes on [`call.php`](call.php)
Suggestions are based on contacts of Microsoft account(s) stored under the provided key(s) (see table below).

Also, [phone books of a FRITZ!Box router](https://service.avm.de/help/en/FRITZ-Box-Fon-WLAN-7490/019/hilfe_fon_telefonbuch) can be integrated using [fritzco](https://github.com/SkyhawkXava/fritzco), see [`config.php`](includes/config.example.php). However, note that fritzco's phone books won't be supported in [`directory.php`](cisco/directory.php) since that's a function fritzco itself offers.

Using query parameters in the URL, you can prefill the input fields:

| parameter | default | example value |
| ------------ | --------------------------------- | ---------------------- |
| `key` | no suggestions based on contacts | `f53fe305ad73b3ff33cf` |
| `key` | suggestions based on contacts from CALL_DEFAULT_KEY in [`config.php`](includes/config.example.php) | `f53fe305ad73b3ff33cf` <br/> (comma-separated if multiple) |
| `devicename` | no selection | `SEP1304E58F0643` |
| `num` | empty | `+12065550100` <br/>(you may need to [URL encode](https://www.urlencoder.org/) this) |
| `ssl` | active | `0` or `1` |

Example: `https://example.org/ipphones/call.php?key=f53fe305ad73b3ff33cf&devicename=SEP1304E58F0643&num=+12065550100&ssl=1`
Example: `https://example.org/ipphones/call.php?key=f53fe305ad73b3ff33cf,bf674bddac25380a20bc&devicename=SEP1304E58F0643&num=+12065550100&ssl=1`

# Notes on storage of keys & connected Microsoft accounts
The information on connected Microsoft accounts is stored in [`storage/`](storage/) on your web server, prefixed with the corresponding key:
Expand Down
129 changes: 90 additions & 39 deletions call.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,25 @@
<div id="page_container">
<h1>Anruf starten</h1>
<?php
$key = get_key();
if($key !== false) {
$access_token = get_access_token($key);
if($access_token === false) { // request access
$keys = array();
if(empty($_GET['key'])) $_GET['key'] = CALL_DEFAULT_KEY;
if(!empty($_GET['key'])) {
$keys = explode(",", $_GET['key']);
foreach ($keys as $key) {
if(array_key_exists($key, FRITZCO_PHONEBOOKS)) continue;

$access_token = get_access_token($key);
if($access_token === false) { // request access
?>
<p> <a href="<?php echo $url_oauth; ?>">
Bitte Microsoft-Konto neu verbinden für Abruf der Kontaktliste!
</a> </p>
<?php
}
}
}
?>
<form method="POST" onsubmit="submit(this)">
<form method="POST" onsubmit="submitXML(this)">
<p>
<label for="target">Telefon:</label><br/>
<select id="target" required autofocus >
Expand Down Expand Up @@ -64,49 +70,94 @@
<input id="tel" type="tel" required list="contacts" value="<?php echo @$_GET['num']; ?>" />
<datalist id="contacts">
<?php
if($key !== false && $access_token !== false) {
query_contacts($access_token, function($json, $last_page) {
if(isset($json->value)) { // multiple results / list
foreach($json->value as $contact) {
$numbers = array();

if(count($contact->homePhones) == 1) {
if(!empty($contact->homePhones[0])) $numbers['Privat'] = $contact->homePhones[0];
}else{
for($i = 0; $i < count($contact->homePhones); $i++) {
if(empty($contact->homePhones[$i])) continue;
$numbers['Privat ' . ($i + 1)] = $contact->homePhones[$i];
}
}
$suggestions = array();

foreach ($keys as $key) {
if(array_key_exists($key, FRITZCO_PHONEBOOKS)) {
if(!empty(FRITZCO_PHONEBOOKS[$key]["url"])) $url = FRITZCO_PHONEBOOKS[$key]["url"];
else $url = FRITZCO_URL;

$book = @file_get_contents($url . "/books/" . FRITZCO_PHONEBOOKS[$key]["bookid"] . ".xml");
if(DEBUG) var_dump($book);

if($book === false) continue;

$xml = simplexml_load_string($book);
if(DEBUG) var_dump($xml);

foreach($xml->phonebook->contact as $contact) {
$name = $contact->person->realName;

foreach($contact->telephony->number as $number) {
$num = preg_replace("/[^+*#\d]/", "", $number);
if(in_array($num, $suggestions)) continue;

if(count($contact->businessPhones) == 1) {
if(!empty($contact->businessPhones[0])) $numbers['Geschäftlich'] = $contact->businessPhones[0];
}else{
for($i = 0; $i < count($contact->businessPhones); $i++) {
if(empty($contact->businessPhones[$i])) continue;
$numbers['Geschäftlich ' . ($i + 1)] = $contact->businessPhones[$i];
}
}
$label = $number->attributes()["type"];
if($label == "home") $label = "Privat";
elseif($label == "work") $label = "Geschäftlich";
elseif($label == "mobile") $label = "Mobil";
elseif($label == "fax_work") $label = "Fax Geschäftlich";

if(!empty($contact->mobilePhone)) {
$numbers['Mobil'] = $contact->mobilePhone;
}

if(count($numbers) > 0) {
$name = $contact->displayName;
if($name === "") $name = $contact->companyName;
?>
<option value="<?php echo $number; ?>">
<?php echo $name . ' (' . $label . ') - ' . $num; ?>
</option>
<?php
}
}

continue;
}

$access_token = get_access_token($key);
if($access_token !== false) {
query_contacts($access_token, function($json, $last_page) {
global $suggestions;

if(isset($json->value)) { // multiple results / list
foreach($json->value as $contact) {
$numbers = array();

if(count($contact->homePhones) == 1) {
if(!empty($contact->homePhones[0])) $numbers['Privat'] = $contact->homePhones[0];
}else{
for($i = 0; $i < count($contact->homePhones); $i++) {
if(empty($contact->homePhones[$i])) continue;
$numbers['Privat ' . ($i + 1)] = $contact->homePhones[$i];
}
}

foreach($numbers as $label => $number) {
if(count($contact->businessPhones) == 1) {
if(!empty($contact->businessPhones[0])) $numbers['Geschäftlich'] = $contact->businessPhones[0];
}else{
for($i = 0; $i < count($contact->businessPhones); $i++) {
if(empty($contact->businessPhones[$i])) continue;
$numbers['Geschäftlich ' . ($i + 1)] = $contact->businessPhones[$i];
}
}

if(!empty($contact->mobilePhone)) {
$numbers['Mobil'] = $contact->mobilePhone;
}

if(count($numbers) > 0) {
$name = $contact->displayName;
if($name === "") $name = $contact->companyName;

foreach($numbers as $label => $number) {
$num = preg_replace("/[^+*#\d]/", "", $number);
array_push($suggestions, $num);
?>
<option value="<?php echo $number; ?>">
<?php echo $name . ' (' . $label . ') - ' . preg_replace("/[^+\d]/", "", $number); ?>
<?php echo $name . ' (' . $label . ') - ' . $num; ?>
</option>
<?php
}
}
}
}
}
});
});
}
}
?>
</datalist>
Expand All @@ -121,7 +172,7 @@
</div>

<script type="text/javascript">
function submit(form) {
function submitXML(form) {
var target = document.getElementById("target").value;
var https = document.getElementById("https").checked;
var tel = document.getElementById("tel").value;
Expand Down
2 changes: 1 addition & 1 deletion cisco/directory.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@

$started = false;
$results = 0;
query_contacts($access_token, function($json, $last_page) {
query_contacts($access_token, function($json, $last_page) { // TODO implement search
global $new_url;
global $started;
global $results;
Expand Down
6 changes: 6 additions & 0 deletions genkey.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?php
require(__DIR__ . "/includes/shared.php");

header("Content-Type: text/plain; charset=utf-8");
echo gen_key();
?>
17 changes: 17 additions & 0 deletions includes/config.example.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,23 @@
)
);

// --- fritzco ---
const FRITZCO_URL = "https://example.com/fritzco";
const FRITZCO_PHONEBOOKS = array(
// open genkey.php in browser to get a random key
"changeme868d490d4e99" => array(
"bookid" => "0"
// 'telefonbuch' number from fritzco's directory.config.inc.php
// (https://github.com/SkyhawkXava/fritzco/blob/master/config/directory.config.inc.php)
),
"random20charhex23159" => array(
"url" => "https://another.example.com/fritzco", // if different than FRITZCO_URL
"bookid" => "240"
// phone books synced with your FRITZ!Box from an external source usually start at 240
)
);

// --- Misc ---
const CALL_DEFAULT_KEY = ""; // Key(s) of addressbooks loaded when none provided in URL
const DEBUG = false;
?>
7 changes: 6 additions & 1 deletion includes/shared.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,11 @@
$url_cisco_auth = pageroot(true) . "cisco/authenticate.php";


function gen_key() {
return substr(sha1(microtime() . random_int(0, 1000)), 0, 20);
}
function get_key() {
if(empty($_GET['key'])) return false;
if(empty($_GET['key'])) return false;
return $_GET['key'];
}

Expand Down Expand Up @@ -58,6 +61,8 @@ function query_contacts($access_token, $foreach_resultpage) {
if(isset($_GET['id'])) {
$query .= '/' . urlencode($_GET['id']);
}else if(isset($_GET['search'])){
// BUG search with > 1 result pages throwing error on 2nd page:
// "The following parameters are not supported with change tracking over the 'Contacts' resource: '$orderby, $filter, $select, $expand, $search, $top'."
$query .= '?$search="' . urlencode($_GET['search']) . '"';
}else{
$query .= '?$orderby=displayName%20asc';
Expand Down
78 changes: 38 additions & 40 deletions index.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,21 @@
für Cisco IP-Phones
</h1>
<?php
$key = get_key();
if($key === false) {
$key = get_key();
if($key !== false && get_access_token($key) === false){
?>
Unter diesem Schlüssel wurde kein Konto gefunden oder es muss neu verknüpft werden. Das können Sie
<a href="<?php echo $url_oauth . "?key=" . $key; ?>">hier</a>
tun.
<?php
}else{
if($key !== false) {
if(isset($_GET['state']) && $_GET['state'] == "authorized") {
?>
<p>Microsoft-Konto erfolgreich verknüpft!</p>
<?php
}
}else{
?>
<form method="GET">
<p>
Expand All @@ -43,52 +56,37 @@
<button id="submit" type="submit">Übersicht anzeigen</button>
</p>
</form>

<br/>

<h2>Ohne Schlüssel nutzbare Funktionen</h2>
<ul>
<li> <a href="<?php echo $url_call; ?>">Anruf auf Telefon starten (Zugangsdaten des Telefons benötigt)</a> </li>
</ul>

<h3>URL für SEP&lt;MAC&gt;.cnf.xml</h3>
<table>
<tr>
<th>authenticationURL</th>
<td><?php echo $url_cisco_auth ?></td>
</tr>
</table>
<?php
}else if(get_access_token($key) === false){
?>
Unter diesem Schlüssel wurde kein Konto gefunden oder es muss neu verknüpft werden. Das können Sie
<a href="<?php echo $url_oauth . "?key=" . $key; ?>">hier</a>
tun.
<?php
}else{
if(isset($_GET['state']) && $_GET['state'] == "authorized") {
?>
<p>Microsoft-Konto erfolgreich verknüpft!</p>
<?php
}
?>
<h2>Per Browser nutzbare Funktionen</h2>
<?php if($key !== false) { ?>
<h2>Per Browser nutzbare Funktionen</h2>
<?php }else{ ?>
<h2>Ohne Schlüssel nutzbare Funktionen</h2>
<?php } ?>
<ul>
<li> <a href="<?php echo $url_call . "?key=" . $key; ?>">Anruf auf Telefon starten (Zugangsdaten des Telefons benötigt)</a> </li>
<li> <a href="<?php echo $url_vcard . $key; ?>">vCard-Export der Kontakte</a> </li>
<li> <a href="<?php echo $url_oauth . "?key=" . $key; ?>">dieses Microsoft-Konto neu verknüpfen</a> </li>
<li> <a href="<?php echo $url_oauth; ?>">ein weiteres Microsoft-Konto verknüpfen</a> </li>
<li> <a href="<?php echo $url_call . ($key !== false ? "?key=" . $key : ""); ?>">Anruf auf Telefon starten (Zugangsdaten des Telefons benötigt)</a> </li>
<?php if($key !== false) { ?>
<li> <a href="<?php echo $url_vcard . $key; ?>">vCard-Export der Kontakte</a> </li>
<li> <a href="<?php echo $url_oauth . "?key=" . $key; ?>">dieses Microsoft-Konto neu verknüpfen</a> </li>
<li> <a href="<?php echo $url_oauth; ?>">ein weiteres Microsoft-Konto verknüpfen</a> </li>
<?php } ?>
</ul>

<br/>

<h2>URLs für SEP&lt;MAC&gt;.cnf.xml</h2>
<?php if($key !== false) { ?>
<br/>
<h2>URLs für SEP&lt;MAC&gt;.cnf.xml</h2>
<?php }else{ ?>
<h3>URL für SEP&lt;MAC&gt;.cnf.xml</h3>
<?php } ?>
<table>
<!-- TODO use secure<...>URL instead? -->
<tr>
<th>directoryURL</th>
<td><?php echo $url_cisco_dir . $key; ?></td>
</tr>
<?php if($key !== false) { ?>
<tr>
<th>directoryURL</th>
<td><?php echo $url_cisco_dir . $key; ?></td>
</tr>
<?php } ?>

<tr>
<th>authenticationURL</th>
Expand Down
2 changes: 1 addition & 1 deletion oauth-grant.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

if(!isset($_GET['key']) || preg_match("/[\da-f]{20,}/", $_GET['key']) !== 1) {
// generate new key
$_GET['key'] = substr(sha1(microtime() . random_int(0, 1000)), 0, 20);
$_GET['key'] = gen_key();
}

$storage->set($_GET['key'] . "_access_token", $response->access_token);
Expand Down
2 changes: 2 additions & 0 deletions vcard.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?php
require(__DIR__ . "/includes/shared.php");

// TODO carddav endpoint

$key = get_key();
if($key === false) { // request key
Expand Down

0 comments on commit 4b655c6

Please sign in to comment.