- Fix silent insert failure in FCM token registration (missing NOT NULL refresh_token column) so WebView app tokens are actually stored - Add 1-minute queue reminder cron that re-sends FCM alerts for calls still waiting, with transient-based throttle to prevent duplicates - Send FCM cancel on queue dequeue (answered/hangup/timeout), not just on final call status webhook - Clean up new cron hook on plugin deactivation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
227 lines
7.7 KiB
PHP
227 lines
7.7 KiB
PHP
<?php
|
|
/**
|
|
* Standalone Mobile Phone Page
|
|
*
|
|
* Registers a front-end endpoint at /twp-phone/ that serves the browser phone UI
|
|
* without any wp-admin chrome. Designed for mobile WebView usage.
|
|
*
|
|
* @package Twilio_WP_Plugin
|
|
*/
|
|
class TWP_Mobile_Phone_Page {
|
|
|
|
/**
|
|
* The endpoint slug.
|
|
*/
|
|
const ENDPOINT = 'twp-phone';
|
|
|
|
/**
|
|
* Constructor — wire up hooks.
|
|
*/
|
|
public function __construct() {
|
|
add_action('init', array($this, 'register_rewrite'));
|
|
add_action('template_redirect', array($this, 'handle_request'));
|
|
add_filter('query_vars', array($this, 'add_query_var'));
|
|
|
|
// Extend session cookie for phone agents.
|
|
add_filter('auth_cookie_expiration', array($this, 'extend_agent_cookie'), 10, 3);
|
|
|
|
// AJAX action for FCM token registration (uses WP cookie auth).
|
|
add_action('wp_ajax_twp_register_fcm_token', array($this, 'ajax_register_fcm_token'));
|
|
}
|
|
|
|
/**
|
|
* AJAX handler: register FCM token for the current user.
|
|
*/
|
|
public function ajax_register_fcm_token() {
|
|
check_ajax_referer('twp_ajax_nonce', 'nonce');
|
|
|
|
$fcm_token = sanitize_text_field($_POST['fcm_token'] ?? '');
|
|
if (empty($fcm_token)) {
|
|
wp_send_json_error('Missing FCM token');
|
|
}
|
|
|
|
$user_id = get_current_user_id();
|
|
if (!$user_id) {
|
|
wp_send_json_error('Not authenticated');
|
|
}
|
|
|
|
// Store FCM token (same as TWP_Mobile_API::register_fcm_token)
|
|
global $wpdb;
|
|
$table = $wpdb->prefix . 'twp_mobile_sessions';
|
|
|
|
// Update existing session or insert new one
|
|
$existing = $wpdb->get_row($wpdb->prepare(
|
|
"SELECT id FROM $table WHERE user_id = %d AND fcm_token = %s AND is_active = 1",
|
|
$user_id, $fcm_token
|
|
));
|
|
|
|
if ($existing) {
|
|
// Refresh the expiry on existing session
|
|
$wpdb->update($table,
|
|
array('expires_at' => date('Y-m-d H:i:s', time() + 7 * DAY_IN_SECONDS)),
|
|
array('id' => $existing->id),
|
|
array('%s'),
|
|
array('%d')
|
|
);
|
|
} else {
|
|
$wpdb->insert($table, array(
|
|
'user_id' => $user_id,
|
|
'refresh_token' => 'webview-' . wp_generate_password(32, false),
|
|
'fcm_token' => $fcm_token,
|
|
'device_info' => 'WebView Mobile App',
|
|
'is_active' => 1,
|
|
'created_at' => current_time('mysql'),
|
|
'expires_at' => date('Y-m-d H:i:s', time() + 7 * DAY_IN_SECONDS),
|
|
));
|
|
|
|
if ($wpdb->last_error) {
|
|
error_log('TWP FCM: Failed to insert token: ' . $wpdb->last_error);
|
|
wp_send_json_error('Failed to store token');
|
|
}
|
|
}
|
|
|
|
error_log("TWP FCM: Token registered for user $user_id");
|
|
wp_send_json_success('FCM token registered');
|
|
}
|
|
|
|
/**
|
|
* Register custom rewrite rule.
|
|
*/
|
|
public function register_rewrite() {
|
|
add_rewrite_rule(
|
|
'^' . self::ENDPOINT . '/?$',
|
|
'index.php?twp_phone_page=1',
|
|
'top'
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Expose query variable.
|
|
*
|
|
* @param array $vars Existing query vars.
|
|
* @return array
|
|
*/
|
|
public function add_query_var($vars) {
|
|
$vars[] = 'twp_phone_page';
|
|
return $vars;
|
|
}
|
|
|
|
/**
|
|
* Handle the request on template_redirect.
|
|
*/
|
|
public function handle_request() {
|
|
if (!get_query_var('twp_phone_page')) {
|
|
return;
|
|
}
|
|
|
|
// Authentication check — redirect to login if not authenticated.
|
|
if (!is_user_logged_in()) {
|
|
$redirect_url = home_url('/' . self::ENDPOINT . '/');
|
|
wp_redirect(wp_login_url($redirect_url));
|
|
exit;
|
|
}
|
|
|
|
// Capability check.
|
|
if (!current_user_can('twp_access_browser_phone')) {
|
|
wp_die(
|
|
'You do not have permission to access the browser phone.',
|
|
'Access Denied',
|
|
array('response' => 403)
|
|
);
|
|
}
|
|
|
|
// Render the standalone page and exit.
|
|
$this->render_page();
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* Extend auth cookie to 7 days for phone agents.
|
|
*
|
|
* @param int $expiration Default expiration in seconds.
|
|
* @param int $user_id User ID.
|
|
* @param bool $remember Whether "Remember Me" was checked.
|
|
* @return int
|
|
*/
|
|
public function extend_agent_cookie($expiration, $user_id, $remember) {
|
|
$user = get_userdata($user_id);
|
|
if ($user && $user->has_cap('twp_access_browser_phone')) {
|
|
return 7 * DAY_IN_SECONDS;
|
|
}
|
|
return $expiration;
|
|
}
|
|
|
|
// ------------------------------------------------------------------
|
|
// Rendering
|
|
// ------------------------------------------------------------------
|
|
|
|
/**
|
|
* Output the complete standalone HTML page.
|
|
*/
|
|
private function render_page() {
|
|
// Gather data needed by the template (same as display_browser_phone_page).
|
|
$current_user_id = get_current_user_id();
|
|
|
|
global $wpdb;
|
|
$extensions_table = $wpdb->prefix . 'twp_user_extensions';
|
|
$extension_data = $wpdb->get_row($wpdb->prepare(
|
|
"SELECT extension FROM $extensions_table WHERE user_id = %d",
|
|
$current_user_id
|
|
));
|
|
|
|
if (!$extension_data) {
|
|
TWP_User_Queue_Manager::create_user_queues($current_user_id);
|
|
$extension_data = $wpdb->get_row($wpdb->prepare(
|
|
"SELECT extension FROM $extensions_table WHERE user_id = %d",
|
|
$current_user_id
|
|
));
|
|
}
|
|
|
|
$agent_status = TWP_Agent_Manager::get_agent_status($current_user_id);
|
|
$agent_stats = TWP_Agent_Manager::get_agent_stats($current_user_id);
|
|
$is_logged_in = TWP_Agent_Manager::is_agent_logged_in($current_user_id);
|
|
|
|
$current_mode = get_user_meta($current_user_id, 'twp_call_mode', true);
|
|
if (empty($current_mode)) {
|
|
$current_mode = 'cell';
|
|
}
|
|
|
|
$user_phone = get_user_meta($current_user_id, 'twp_phone_number', true);
|
|
|
|
// Smart routing check (for admin-only setup notice).
|
|
$smart_routing_configured = false;
|
|
try {
|
|
$twilio = new TWP_Twilio_API();
|
|
$phone_numbers = $twilio->get_phone_numbers();
|
|
if ($phone_numbers['success']) {
|
|
$smart_routing_url = home_url('/wp-json/twilio-webhook/v1/smart-routing');
|
|
foreach ($phone_numbers['data']['incoming_phone_numbers'] as $number) {
|
|
if (isset($number['voice_url']) && strpos($number['voice_url'], 'smart-routing') !== false) {
|
|
$smart_routing_configured = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} catch (Exception $e) {
|
|
// Silently continue.
|
|
}
|
|
|
|
// Nonce for AJAX.
|
|
$nonce = wp_create_nonce('twp_ajax_nonce');
|
|
|
|
// URLs.
|
|
$ajax_url = admin_url('admin-ajax.php');
|
|
$ringtone_url = plugins_url('assets/sounds/ringtone.mp3', dirname(__FILE__));
|
|
$phone_icon_url = plugins_url('assets/images/phone-icon.png', dirname(__FILE__));
|
|
$sw_url = plugins_url('assets/js/twp-service-worker.js', dirname(__FILE__));
|
|
$twilio_edge = esc_js(get_option('twp_twilio_edge', 'roaming'));
|
|
$smart_routing_webhook = home_url('/wp-json/twilio-webhook/v1/smart-routing');
|
|
|
|
// Plugin file reference for plugins_url() in template.
|
|
$plugin_file = dirname(__FILE__) . '/../twilio-wp-plugin.php';
|
|
|
|
// Load the template (all variables above are in scope).
|
|
require TWP_PLUGIN_DIR . 'assets/mobile/phone-template.php';
|
|
}
|
|
}
|