Files
twilio-wp-plugin/admin/mobile-app-settings.php
Claude 5adfa694c1
All checks were successful
Create Release / build (push) Successful in 3s
Migrate FCM from legacy v1 API to HTTP v2 with service account auth
Replace deprecated FCM server key authentication with Google service
account OAuth2 flow. The class now creates a signed JWT from the
service account credentials, exchanges it for a short-lived access
token (cached via WordPress transients), and sends messages to the
FCM v2 endpoint (projects/{id}/messages:send).

Settings page updated: FCM Server Key field replaced with Firebase
Project ID + Service Account JSON textarea with validation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 14:04:33 -08:00

451 lines
20 KiB
PHP

<?php
/**
* Mobile App Settings Page
*/
// Prevent direct access
if (!defined('WPINC')) {
die;
}
// Check user capabilities
if (!current_user_can('manage_options')) {
wp_die(__('You do not have sufficient permissions to access this page.'));
}
// Handle manual update check
if (isset($_POST['twp_check_updates']) && check_admin_referer('twp_mobile_settings')) {
require_once TWP_PLUGIN_DIR . 'includes/class-twp-auto-updater.php';
$updater = new TWP_Auto_Updater();
$update_result = $updater->manual_check_for_updates();
}
// Handle test notification
if (isset($_POST['twp_test_notification']) && check_admin_referer('twp_mobile_settings')) {
require_once TWP_PLUGIN_DIR . 'includes/class-twp-fcm.php';
$fcm = new TWP_FCM();
$test_user_id = get_current_user_id();
$notification_sent = $fcm->send_test_notification($test_user_id);
if ($notification_sent) {
$notification_result = array('success' => true, 'message' => 'Test notification sent successfully!');
} else {
$notification_result = array('success' => false, 'message' => 'Failed to send test notification. Check FCM configuration.');
}
}
// Save settings
if (isset($_POST['twp_save_mobile_settings']) && check_admin_referer('twp_mobile_settings')) {
update_option('twp_fcm_project_id', sanitize_text_field($_POST['twp_fcm_project_id']));
// Service account JSON — validate it parses as JSON before saving
$sa_json_raw = isset($_POST['twp_fcm_service_account_json']) ? wp_unslash($_POST['twp_fcm_service_account_json']) : '';
if (!empty($sa_json_raw)) {
$sa_parsed = json_decode($sa_json_raw, true);
if ($sa_parsed && isset($sa_parsed['client_email'], $sa_parsed['private_key'])) {
update_option('twp_fcm_service_account_json', $sa_json_raw);
} else {
$sa_json_error = 'Invalid service account JSON — must contain client_email and private_key fields.';
}
} else {
update_option('twp_fcm_service_account_json', '');
}
update_option('twp_auto_update_enabled', isset($_POST['twp_auto_update_enabled']) ? '1' : '0');
update_option('twp_gitea_repo', sanitize_text_field($_POST['twp_gitea_repo']));
update_option('twp_gitea_token', sanitize_text_field($_POST['twp_gitea_token']));
update_option('twp_twilio_api_key_sid', sanitize_text_field($_POST['twp_twilio_api_key_sid']));
update_option('twp_twilio_api_key_secret', sanitize_text_field($_POST['twp_twilio_api_key_secret']));
update_option('twp_fcm_push_credential_sid', sanitize_text_field($_POST['twp_fcm_push_credential_sid']));
$settings_saved = true;
}
// Get current settings
$fcm_project_id = get_option('twp_fcm_project_id', '');
$fcm_service_account_json = get_option('twp_fcm_service_account_json', '');
$fcm_sa_configured = !empty($fcm_service_account_json) && !empty($fcm_project_id);
$auto_update_enabled = get_option('twp_auto_update_enabled', '1') === '1';
$gitea_repo = get_option('twp_gitea_repo', 'wp-plugins/twilio-wp-plugin');
$gitea_token = get_option('twp_gitea_token', '');
$twilio_api_key_sid = get_option('twp_twilio_api_key_sid', '');
$twilio_api_key_secret = get_option('twp_twilio_api_key_secret', '');
$fcm_push_credential_sid = get_option('twp_fcm_push_credential_sid', '');
// Get update status
require_once TWP_PLUGIN_DIR . 'includes/class-twp-auto-updater.php';
$updater = new TWP_Auto_Updater();
$update_status = $updater->get_update_status();
// Get mobile app statistics
global $wpdb;
$sessions_table = $wpdb->prefix . 'twp_mobile_sessions';
$active_sessions = $wpdb->get_var("SELECT COUNT(*) FROM $sessions_table WHERE is_active = 1 AND expires_at > NOW()");
$total_sessions = $wpdb->get_var("SELECT COUNT(*) FROM $sessions_table");
?>
<div class="wrap">
<h1><?php echo esc_html(get_admin_page_title()); ?></h1>
<?php if (isset($settings_saved)): ?>
<div class="notice notice-success is-dismissible">
<p><strong>Settings saved successfully!</strong></p>
</div>
<?php endif; ?>
<?php if (isset($update_result)): ?>
<div class="notice notice-<?php echo $update_result['update_available'] ? 'warning' : 'success'; ?> is-dismissible">
<p><strong><?php echo esc_html($update_result['message']); ?></strong></p>
</div>
<?php endif; ?>
<?php if (isset($notification_result)): ?>
<div class="notice notice-<?php echo $notification_result['success'] ? 'success' : 'error'; ?> is-dismissible">
<p><strong><?php echo esc_html($notification_result['message']); ?></strong></p>
</div>
<?php endif; ?>
<?php if (isset($sa_json_error)): ?>
<div class="notice notice-error is-dismissible">
<p><strong><?php echo esc_html($sa_json_error); ?></strong></p>
</div>
<?php endif; ?>
<div class="twp-mobile-settings">
<!-- Mobile App Overview -->
<div class="card" style="max-width: 100%; margin-bottom: 20px;">
<h2>Mobile App Overview</h2>
<table class="widefat">
<tbody>
<tr>
<td><strong>API Endpoint:</strong></td>
<td><code><?php echo esc_html(site_url('/wp-json/twilio-mobile/v1')); ?></code></td>
</tr>
<tr>
<td><strong>Active Sessions:</strong></td>
<td><?php echo esc_html($active_sessions); ?> active / <?php echo esc_html($total_sessions); ?> total</td>
</tr>
<tr>
<td><strong>Plugin Version:</strong></td>
<td><?php echo esc_html(TWP_VERSION); ?></td>
</tr>
</tbody>
</table>
</div>
<!-- Mobile App Settings Form -->
<form method="post" action="">
<?php wp_nonce_field('twp_mobile_settings'); ?>
<!-- FCM Configuration -->
<div class="card" style="max-width: 100%; margin-bottom: 20px;">
<h2>Firebase Cloud Messaging (FCM) — HTTP v2 API</h2>
<p>Configure FCM using a service account for push notifications. The legacy server key API has been retired by Google.</p>
<table class="form-table">
<tr>
<th scope="row">
<label for="twp_fcm_project_id">Firebase Project ID</label>
</th>
<td>
<input type="text"
id="twp_fcm_project_id"
name="twp_fcm_project_id"
value="<?php echo esc_attr($fcm_project_id); ?>"
class="regular-text"
placeholder="my-project-12345">
<p class="description">
Found in Firebase Console &gt; Project Settings &gt; General &gt; Project ID
</p>
</td>
</tr>
<tr>
<th scope="row">
<label for="twp_fcm_service_account_json">Service Account JSON</label>
</th>
<td>
<textarea id="twp_fcm_service_account_json"
name="twp_fcm_service_account_json"
rows="6"
class="large-text code"
placeholder='Paste the entire contents of your service account JSON file...'><?php echo esc_textarea($fcm_service_account_json); ?></textarea>
<p class="description">
Generate in Firebase Console &gt; Project Settings &gt; Service Accounts &gt; Generate New Private Key.
Paste the entire JSON file contents here. Must contain <code>client_email</code> and <code>private_key</code> fields.
</p>
<?php if ($fcm_sa_configured): ?>
<p style="color: #00a32a; margin-top: 5px;">&#10003; Service account configured</p>
<?php endif; ?>
</td>
</tr>
<tr>
<th scope="row">
<label for="twp_twilio_api_key_sid">Twilio API Key SID</label>
</th>
<td>
<input type="text"
id="twp_twilio_api_key_sid"
name="twp_twilio_api_key_sid"
value="<?php echo esc_attr($twilio_api_key_sid); ?>"
class="regular-text"
placeholder="SK...">
<p class="description">
Create an API Key in Twilio Console &gt; Account &gt; API Keys. Required for mobile VoIP tokens.
</p>
</td>
</tr>
<tr>
<th scope="row">
<label for="twp_twilio_api_key_secret">Twilio API Key Secret</label>
</th>
<td>
<input type="password"
id="twp_twilio_api_key_secret"
name="twp_twilio_api_key_secret"
value="<?php echo esc_attr($twilio_api_key_secret); ?>"
class="regular-text">
<p class="description">
The secret associated with the API Key SID above. Shown only once when key is created.
</p>
</td>
</tr>
<tr>
<th scope="row">
<label for="twp_fcm_push_credential_sid">Push Credential SID</label>
</th>
<td>
<input type="text"
id="twp_fcm_push_credential_sid"
name="twp_fcm_push_credential_sid"
value="<?php echo esc_attr($fcm_push_credential_sid); ?>"
class="regular-text"
placeholder="CR...">
<p class="description">
Twilio Push Credential SID. Create in Twilio Console &gt; Messaging &gt; Push Credentials using your FCM service account JSON. Required for incoming call push notifications.
</p>
</td>
</tr>
</table>
<?php if ($fcm_sa_configured): ?>
<p>
<button type="submit" name="twp_test_notification" class="button">
Send Test Notification
</button>
<span class="description">Send a test notification to your devices</span>
</p>
<?php endif; ?>
</div>
<!-- Auto-Update Settings -->
<div class="card" style="max-width: 100%; margin-bottom: 20px;">
<h2>Automatic Updates</h2>
<table class="form-table">
<tr>
<th scope="row">Current Version</th>
<td>
<strong><?php echo esc_html($update_status['current_version']); ?></strong>
<?php if ($update_status['update_available']): ?>
<span style="color: #d63638; margin-left: 10px;">
⚠ Update available: <?php echo esc_html($update_status['latest_version']); ?>
</span>
<?php else: ?>
<span style="color: #00a32a; margin-left: 10px;">✓ Up to date</span>
<?php endif; ?>
</td>
</tr>
<tr>
<th scope="row">
<label for="twp_auto_update_enabled">Enable Auto-Updates</label>
</th>
<td>
<label>
<input type="checkbox"
id="twp_auto_update_enabled"
name="twp_auto_update_enabled"
value="1"
<?php checked($auto_update_enabled); ?>>
Automatically check for updates every 12 hours
</label>
</td>
</tr>
<tr>
<th scope="row">
<label for="twp_gitea_repo">Gitea Repository</label>
</th>
<td>
<input type="text"
id="twp_gitea_repo"
name="twp_gitea_repo"
value="<?php echo esc_attr($gitea_repo); ?>"
class="regular-text"
placeholder="org/repo-name">
<p class="description">
Format: organization/repository (e.g., wp-plugins/twilio-wp-plugin)
</p>
</td>
</tr>
<tr>
<th scope="row">
<label for="twp_gitea_token">Gitea Access Token</label>
</th>
<td>
<input type="password"
id="twp_gitea_token"
name="twp_gitea_token"
value="<?php echo esc_attr($gitea_token); ?>"
class="regular-text"
placeholder="">
<p class="description">
Optional. Required only for private repositories. Create token at:
<a href="https://repo.anhonesthost.net/user/settings/applications" target="_blank">Gitea Settings > Applications</a>
</p>
</td>
</tr>
<tr>
<th scope="row">Last Update Check</th>
<td>
<?php
$last_check = $update_status['last_check'];
if ($last_check > 0) {
echo esc_html(human_time_diff($last_check, current_time('timestamp')) . ' ago');
} else {
echo 'Never';
}
?>
<button type="submit" name="twp_check_updates" class="button" style="margin-left: 15px;">
Check Now
</button>
</td>
</tr>
</table>
</div>
<!-- API Documentation -->
<div class="card" style="max-width: 100%; margin-bottom: 20px;">
<h2>API Endpoints</h2>
<p>Available REST API endpoints for mobile app development:</p>
<table class="widefat striped">
<thead>
<tr>
<th>Endpoint</th>
<th>Method</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>/twilio-mobile/v1/auth/login</code></td>
<td>POST</td>
<td>Authenticate and get JWT tokens</td>
</tr>
<tr>
<td><code>/twilio-mobile/v1/auth/refresh</code></td>
<td>POST</td>
<td>Refresh access token</td>
</tr>
<tr>
<td><code>/twilio-mobile/v1/agent/status</code></td>
<td>GET/POST</td>
<td>Get or update agent status</td>
</tr>
<tr>
<td><code>/twilio-mobile/v1/queues/state</code></td>
<td>GET</td>
<td>Get all queue states</td>
</tr>
<tr>
<td><code>/twilio-mobile/v1/calls/{call_sid}/accept</code></td>
<td>POST</td>
<td>Accept a queued call</td>
</tr>
<tr>
<td><code>/twilio-mobile/v1/stream/events</code></td>
<td>GET</td>
<td>Server-Sent Events stream for real-time updates</td>
</tr>
<tr>
<td><code>/twilio-mobile/v1/voice/token</code></td>
<td>GET</td>
<td>Get Twilio Voice access token for VoIP</td>
</tr>
</tbody>
</table>
<p style="margin-top: 15px;">
<strong>Authentication:</strong> All endpoints (except login/refresh) require
<code>Authorization: Bearer &lt;access_token&gt;</code> header.
</p>
</div>
<p class="submit">
<button type="submit" name="twp_save_mobile_settings" class="button button-primary">
Save Settings
</button>
</p>
</form>
<!-- Active Sessions -->
<?php if ($active_sessions > 0): ?>
<div class="card" style="max-width: 100%; margin-bottom: 20px;">
<h2>Active Mobile Sessions</h2>
<?php
$sessions = $wpdb->get_results("
SELECT s.user_id, s.device_info, s.logged_in_at, s.last_used, u.user_login, u.display_name
FROM $sessions_table s
JOIN {$wpdb->users} u ON s.user_id = u.ID
WHERE s.is_active = 1 AND s.expires_at > NOW()
ORDER BY s.last_used DESC
LIMIT 20
");
?>
<table class="widefat striped">
<thead>
<tr>
<th>User</th>
<th>Device</th>
<th>Last Activity</th>
</tr>
</thead>
<tbody>
<?php foreach ($sessions as $session): ?>
<tr>
<td><?php echo esc_html($session->display_name ?: $session->user_login); ?></td>
<td><?php echo esc_html($session->device_info ?: 'Unknown device'); ?></td>
<td><?php echo esc_html(human_time_diff(strtotime($session->last_used), current_time('timestamp')) . ' ago'); ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</div>
</div>
<style>
.twp-mobile-settings .card {
padding: 20px;
background: #fff;
border: 1px solid #ccd0d4;
box-shadow: 0 1px 1px rgba(0,0,0,.04);
}
.twp-mobile-settings .card h2 {
margin-top: 0;
padding-bottom: 10px;
border-bottom: 1px solid #f0f0f1;
}
.twp-mobile-settings code {
background: #f0f0f1;
padding: 2px 6px;
border-radius: 3px;
font-size: 13px;
}
.twp-mobile-settings table.widefat td code {
background: #f6f7f7;
}
</style>