Skip to content

Commit bf423b2

Browse files
committed
feat: Make activesync admin screen more helpful when activesync is unavailable
1 parent efa4259 commit bf423b2

2 files changed

Lines changed: 230 additions & 97 deletions

File tree

admin/activesync.php

Lines changed: 133 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -14,123 +14,159 @@
1414
* @package Horde
1515
*/
1616

17+
use Horde\Util\HordeString;
1718
use Horde\Util\Util;
1819

1920
require_once __DIR__ . '/../lib/Application.php';
2021
Horde_Registry::appInit('horde', [
2122
'permission' => ['horde:administration:activesync'],
2223
]);
2324

25+
// Build diagnostic status (always shown)
26+
$status = [];
27+
$status['package'] = class_exists('Horde_ActiveSync_State_Sql')
28+
|| class_exists('Horde_ActiveSync_State_Mongo');
29+
$status['enabled'] = !empty($conf['activesync']['enabled']);
30+
$status['storage'] = $conf['activesync']['storage'] ?? '';
31+
$status['database'] = null;
32+
if ($status['enabled'] && HordeString::lower($status['storage'] ?: 'sql') === 'sql') {
33+
try {
34+
$injector->getInstance('Horde_Core_Factory_Db')
35+
->create('horde', 'activesync');
36+
$status['database'] = true;
37+
} catch (Throwable $e) {
38+
$status['database'] = $e->getMessage();
39+
}
40+
}
41+
42+
// Try to get the ActiveSync state driver
43+
$state = null;
2444
try {
2545
$state = $injector->getInstance('Horde_ActiveSyncState');
2646
} catch (Horde_Exception $e) {
27-
throw new Horde_Exception_PermissionDenied(_("ActiveSync not activated."));
2847
}
2948

30-
$state->setLogger($injector->getInstance('Horde_Log_Logger'));
31-
32-
/** Check for any actions **/
33-
if ($actionID = Util::getPost('actionID')) {
34-
$deviceID = Util::getPost('deviceID');
35-
36-
$device_desc = explode(':', $deviceID);
37-
$deviceID = $device_desc[0];
38-
39-
switch ($actionID) {
40-
case 'wipe':
41-
$state->setDeviceRWStatus($deviceID, Horde_ActiveSync::RWSTATUS_PENDING);
42-
$GLOBALS['notification']->push(_("A device wipe has been requested. Device will be wiped on next syncronization attempt."), 'horde.success');
43-
break;
44-
45-
case 'cancelwipe':
46-
$state->setDeviceRWStatus($deviceID, Horde_ActiveSync::RWSTATUS_OK);
47-
$GLOBALS['notification']->push(_("Device wipe successfully canceled."), 'horde.success');
48-
break;
49-
50-
case 'delete':
51-
$state->removeState(
52-
[
53-
'devId' => $deviceID,
54-
'user' => Util::getPost('uid')]
55-
);
56-
$GLOBALS['notification']->push(_("Device successfully removed."), 'horde.success');
57-
break;
58-
59-
case 'reset':
60-
$state->resetAllPolicyKeys();
61-
$GLOBALS['notification']->push(_("All policy keys successfully reset."), 'horde.success');
62-
break;
63-
64-
case 'block':
65-
$device = $state->loadDeviceInfo($deviceID);
66-
$device->blocked = true;
67-
$device->save(false);
68-
break;
69-
70-
case 'unblock':
71-
$device = $state->loadDeviceInfo($deviceID);
72-
$device->blocked = false;
73-
$device->save(false);
74-
break;
49+
// Process device actions if ActiveSync is operational
50+
if ($state) {
51+
$state->setLogger($injector->getInstance('Horde_Log_Logger'));
52+
53+
if ($actionID = Util::getPost('actionID')) {
54+
$deviceID = Util::getPost('deviceID');
55+
56+
$device_desc = explode(':', $deviceID);
57+
$deviceID = $device_desc[0];
58+
59+
switch ($actionID) {
60+
case 'wipe':
61+
$state->setDeviceRWStatus($deviceID, Horde_ActiveSync::RWSTATUS_PENDING);
62+
$GLOBALS['notification']->push(_("A device wipe has been requested. Device will be wiped on next syncronization attempt."), 'horde.success');
63+
break;
64+
65+
case 'cancelwipe':
66+
$state->setDeviceRWStatus($deviceID, Horde_ActiveSync::RWSTATUS_OK);
67+
$GLOBALS['notification']->push(_("Device wipe successfully canceled."), 'horde.success');
68+
break;
69+
70+
case 'delete':
71+
$state->removeState(
72+
[
73+
'devId' => $deviceID,
74+
'user' => Util::getPost('uid')]
75+
);
76+
$GLOBALS['notification']->push(_("Device successfully removed."), 'horde.success');
77+
break;
78+
79+
case 'reset':
80+
$state->resetAllPolicyKeys();
81+
$GLOBALS['notification']->push(_("All policy keys successfully reset."), 'horde.success');
82+
break;
83+
84+
case 'block':
85+
$device = $state->loadDeviceInfo($deviceID);
86+
$device->blocked = true;
87+
$device->save(false);
88+
break;
89+
90+
case 'unblock':
91+
$device = $state->loadDeviceInfo($deviceID);
92+
$device->blocked = false;
93+
$device->save(false);
94+
break;
95+
}
7596
}
7697
}
7798

78-
switch (Util::getPost('searchBy')) {
79-
case 'username':
80-
$devices = $state->listDevices(Util::getPost('searchInput'));
81-
break;
99+
// Render page
100+
$page_output->addThemeStylesheet('settings.css');
101+
$page_output->header(['title' => _("ActiveSync Administration")]);
102+
require HORDE_TEMPLATES . '/admin/menu.inc';
82103

83-
default:
84-
$devices = $state->listDevices(null, [Util::getPost('searchBy') => Util::getPost('searchInput')]);
85-
}
104+
// Always render status cards
105+
$statusView = new Horde_View([
106+
'templatePath' => HORDE_TEMPLATES . '/admin',
107+
]);
108+
$statusView->addHelper('Tag');
109+
$statusView->addHelper('Text');
110+
$statusView->status = $status;
111+
$statusView->configUrl = Horde::url('admin/config/config.php')->add('app', 'horde');
112+
echo $statusView->render('activesync_status');
113+
114+
// Render device list if ActiveSync is operational
115+
if ($state) {
116+
switch (Util::getPost('searchBy')) {
117+
case 'username':
118+
$devices = $state->listDevices(Util::getPost('searchInput'));
119+
break;
86120

87-
$view = new Horde_View([
88-
'templatePath' => [HORDE_TEMPLATES . '/admin', HORDE_TEMPLATES . '/activesync']]);
89-
$view->addHelper('Tag');
90-
91-
$selfurl = Horde::selfUrl();
92-
$view->reset = $selfurl->copy()->add('reset', 1);
93-
$devs = [];
94-
$js = [];
95-
$collections = [];
96-
foreach (array_values($devices) as $device) {
97-
$dev = $state->loadDeviceInfo($device['device_id'], $device['device_user']);
98-
try {
99-
$dev = $GLOBALS['injector']->getInstance('Horde_Core_Hooks')
100-
->callHook('activesync_device_modify', 'horde', [$dev]);
101-
} catch (Horde_Exception_HookNotSet $e) {
121+
default:
122+
$devices = $state->listDevices(null, [Util::getPost('searchBy') => Util::getPost('searchInput')]);
102123
}
103-
$syncCache = new Horde_ActiveSync_SyncCache($state, $dev->id, $dev->user, $injector->getInstance('Horde_Log_Logger'));
104-
$dev->hbinterval = $syncCache->hbinterval
105-
? $syncCache->hbinterval
106-
: ($syncCache->wait ? $syncCache->wait * 60 : _("Unavailable"));
107-
$js[$dev->id . ':' . $dev->user] = [
108-
'id' => $dev->id,
109-
'user' => $dev->user,
110-
];
111-
$devs[] = $dev;
112-
$collection = [];
113-
foreach ($syncCache->getCollections() as $id => $c) {
114-
$collection[] = [
115-
_("Collection id") => $id,
116-
_("Class") => $c['class'],
117-
_("Server Id") => $c['serverid'],
118-
_("Last synckey") => $c['lastsynckey'],
124+
125+
$view = new Horde_View([
126+
'templatePath' => [HORDE_TEMPLATES . '/admin', HORDE_TEMPLATES . '/activesync']]);
127+
$view->addHelper('Tag');
128+
129+
$selfurl = Horde::selfUrl();
130+
$view->reset = $selfurl->copy()->add('reset', 1);
131+
$devs = [];
132+
$js = [];
133+
$collections = [];
134+
foreach (array_values($devices) as $device) {
135+
$dev = $state->loadDeviceInfo($device['device_id'], $device['device_user']);
136+
try {
137+
$dev = $GLOBALS['injector']->getInstance('Horde_Core_Hooks')
138+
->callHook('activesync_device_modify', 'horde', [$dev]);
139+
} catch (Horde_Exception_HookNotSet $e) {
140+
}
141+
$syncCache = new Horde_ActiveSync_SyncCache($state, $dev->id, $dev->user, $injector->getInstance('Horde_Log_Logger'));
142+
$dev->hbinterval = $syncCache->hbinterval
143+
? $syncCache->hbinterval
144+
: ($syncCache->wait ? $syncCache->wait * 60 : _("Unavailable"));
145+
$js[$dev->id . ':' . $dev->user] = [
146+
'id' => $dev->id,
147+
'user' => $dev->user,
119148
];
149+
$devs[] = $dev;
150+
$collection = [];
151+
foreach ($syncCache->getCollections() as $id => $c) {
152+
$collection[] = [
153+
_("Collection id") => $id,
154+
_("Class") => $c['class'],
155+
_("Server Id") => $c['serverid'],
156+
_("Last synckey") => $c['lastsynckey'],
157+
];
158+
}
159+
$collections[] = $collection;
120160
}
121-
$collections[] = $collection;
161+
$view->devices = $devs;
162+
$view->collections = $collections;
163+
$view->isAdmin = true;
164+
165+
$page_output->addScriptFile('activesyncadmin.js', 'horde');
166+
$page_output->addInlineJsVars([
167+
'HordeActiveSyncAdmin.devices' => $js,
168+
]);
169+
echo $view->render('activesync');
122170
}
123-
$view->devices = $devs;
124-
$view->collections = $collections;
125-
$view->isAdmin = true;
126-
$page_output->header([
127-
'title' => _("ActiveSync Administration"),
128-
]);
129171

130-
require HORDE_TEMPLATES . '/admin/menu.inc';
131-
$page_output->addScriptFile('activesyncadmin.js', 'horde');
132-
$page_output->addInlineJsVars([
133-
'HordeActiveSyncAdmin.devices' => $js,
134-
]);
135-
echo $view->render('activesync');
136172
echo $page_output->footer();
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
<div class="settings-page">
2+
<h1 class="settings-page-title"><?php echo _("ActiveSync Administration") ?></h1>
3+
<?php $s = $this->status; ?>
4+
<?php $allOk = $s['package'] && $s['enabled'] && $s['storage'] && $s['database'] !== false && !is_string($s['database']); ?>
5+
<?php if (!$allOk): ?>
6+
<p><?php echo _("ActiveSync is not fully operational. The following diagnostics show what is missing.") ?></p>
7+
<?php endif ?>
8+
9+
<ul class="settings-status-list">
10+
11+
<li class="settings-status-card">
12+
<div class="settings-status-card-header">
13+
<span class="settings-status-card-label"><?php echo _("ActiveSync Package") ?></span>
14+
<?php if ($s['package']): ?>
15+
<span class="settings-status settings-status-ok"><?php echo _("Installed") ?></span>
16+
<?php else: ?>
17+
<span class="settings-status settings-status-error"><?php echo _("Missing") ?></span>
18+
<?php endif ?>
19+
</div>
20+
<?php if (!$s['package']): ?>
21+
<div class="settings-status-card-detail">
22+
<?php echo _("The horde/activesync package is not installed. Install it via:") ?>
23+
<code class="settings-path">composer require horde/activesync</code>
24+
</div>
25+
<?php endif ?>
26+
</li>
27+
28+
<li class="settings-status-card">
29+
<div class="settings-status-card-header">
30+
<span class="settings-status-card-label"><?php echo _("Configuration") ?></span>
31+
<?php if ($s['enabled']): ?>
32+
<span class="settings-status settings-status-ok"><?php echo _("Enabled") ?></span>
33+
<?php else: ?>
34+
<span class="settings-status settings-status-error"><?php echo _("Disabled") ?></span>
35+
<?php endif ?>
36+
</div>
37+
<?php if (!$s['enabled']): ?>
38+
<div class="settings-status-card-detail">
39+
<?php echo sprintf(
40+
_("ActiveSync is not enabled. Enable it in %sHorde Settings%s under the ActiveSync tab."),
41+
'<a href="' . $this->h($this->configUrl) . '">',
42+
'</a>'
43+
) ?>
44+
</div>
45+
<?php endif ?>
46+
</li>
47+
48+
<li class="settings-status-card">
49+
<div class="settings-status-card-header">
50+
<span class="settings-status-card-label"><?php echo _("Storage Backend") ?></span>
51+
<?php if ($s['storage']): ?>
52+
<span class="settings-status settings-status-ok"><?php echo $this->h($s['storage']) ?></span>
53+
<?php elseif ($s['enabled']): ?>
54+
<span class="settings-status settings-status-warning"><?php echo _("Not configured") ?></span>
55+
<?php else: ?>
56+
<span class="settings-status settings-status-na"><?php echo _("N/A") ?></span>
57+
<?php endif ?>
58+
</div>
59+
<?php if ($s['enabled'] && !$s['storage']): ?>
60+
<div class="settings-status-card-detail">
61+
<?php echo sprintf(
62+
_("No storage backend configured. Configure it in %sHorde Settings%s under the ActiveSync tab."),
63+
'<a href="' . $this->h($this->configUrl) . '">',
64+
'</a>'
65+
) ?>
66+
</div>
67+
<?php endif ?>
68+
</li>
69+
70+
<li class="settings-status-card">
71+
<div class="settings-status-card-header">
72+
<span class="settings-status-card-label"><?php echo _("Database Connection") ?></span>
73+
<?php if ($s['database'] === null): ?>
74+
<span class="settings-status settings-status-na"><?php echo _("N/A") ?></span>
75+
<?php elseif ($s['database'] === true): ?>
76+
<span class="settings-status settings-status-ok"><?php echo _("OK") ?></span>
77+
<?php else: ?>
78+
<span class="settings-status settings-status-error"><?php echo _("Failed") ?></span>
79+
<?php endif ?>
80+
</div>
81+
<?php if (is_string($s['database'])): ?>
82+
<div class="settings-status-card-detail">
83+
<?php echo $this->h($s['database']) ?>
84+
</div>
85+
<?php elseif ($s['database'] === null && !$s['enabled']): ?>
86+
<div class="settings-status-card-detail">
87+
<?php echo _("Enable ActiveSync first to test database connectivity.") ?>
88+
</div>
89+
<?php endif ?>
90+
</li>
91+
</ul>
92+
93+
<div class="settings-status-card-detail" style="margin-top: 1em;">
94+
<strong><?php echo _("Web Server Requirement:") ?></strong>
95+
<?php echo _("ActiveSync requires a web server alias mapping /Microsoft-Server-ActiveSync to /horde/rpc.php.") ?>
96+
</div>
97+
</div>

0 commit comments

Comments
 (0)