'247chatmate', 'matewa_saved_section' => 'agency', 'matewa_scroll_target' => 'agency_remote_push', ), is_array($extra_args) ? $extra_args : array()); if ('' !== (string) $notice) { $args['matewa_agency_remote_push_notice'] = sanitize_key((string) $notice); } return add_query_arg($args, admin_url('admin.php')); } } if (!function_exists('matewa_agency_remote_push_redirect')) { function matewa_agency_remote_push_redirect($notice, $extra_args = array()) { wp_safe_redirect(matewa_agency_remote_push_redirect_url($notice, $extra_args)); exit; } } if (!function_exists('matewa_agency_remote_push_notice_message')) { function matewa_agency_remote_push_notice_message($code = '') { $code = sanitize_key((string) $code); $messages = array( 'remote_push_ok' => 'Setup pushed and imported on the client site. The one-time setup key was consumed.', 'remote_push_test_ok' => 'Local loopback test passed. The selected setup pack imported locally and the temporary setup key was consumed.', 'remote_push_test_failed' => 'Local loopback test failed. Review the last push details below.', 'remote_push_test_missing_pack' => 'Choose a saved setup pack before running the local loopback test.', 'remote_push_missing_fields' => 'Choose a setup pack, add the client domain or HTTPS URL, and paste the one-time setup key.', 'remote_push_invalid_url' => 'Use the client domain or HTTPS URL, for example clientdomain.co.uk.', 'remote_push_agency_required' => 'Agency Hub access is required before pushing client setup packs.', 'remote_push_pack_not_found' => 'The selected client setup pack could not be found.', 'remote_push_payload_failed' => 'The setup JSON payload could not be built from that client setup pack.', 'remote_push_request_failed' => 'The client site could not be reached. Confirm the plugin is installed and the URL is correct.', 'remote_push_client_rejected' => 'The client plugin rejected the setup push. Check the one-time setup key and try again.', 'remote_push_client_failed' => 'The client plugin received the request but could not import the setup.', 'remote_push_key_consumed' => 'The client setup key has already been used, revoked or expired. Generate a new key on the client site.', ); return isset($messages[$code]) ? $messages[$code] : ('' !== $code ? 'Remote setup push status: ' . str_replace('_', ' ', $code) . '.' : ''); } } if (!function_exists('matewa_agency_remote_push_success_codes')) { function matewa_agency_remote_push_success_codes() { return array('remote_push_ok', 'remote_push_test_ok'); } } if (!function_exists('matewa_agency_remote_push_get_last')) { function matewa_agency_remote_push_get_last() { $last = get_option(MATEWA_AGENCY_REMOTE_PUSH_LAST_OPTION, array()); return is_array($last) ? $last : array(); } } if (!function_exists('matewa_agency_remote_push_store_last')) { function matewa_agency_remote_push_store_last($data) { $data = is_array($data) ? $data : array(); $safe = array( 'checkpoint' => isset($data['checkpoint']) ? sanitize_key((string) $data['checkpoint']) : 'AGENCYCLIENTFREEMIUMOVERRIDEFIX1', 'attempted_at' => isset($data['attempted_at']) ? absint($data['attempted_at']) : matewa_agency_remote_push_now(), 'request_id' => isset($data['request_id']) ? sanitize_text_field((string) $data['request_id']) : '', 'pack_id' => isset($data['pack_id']) ? sanitize_key((string) $data['pack_id']) : '', 'client_site_url' => isset($data['client_site_url']) ? esc_url_raw((string) $data['client_site_url']) : '', 'endpoint_url' => isset($data['endpoint_url']) ? esc_url_raw((string) $data['endpoint_url']) : '', 'ok' => !empty($data['ok']), 'notice' => isset($data['notice']) ? sanitize_key((string) $data['notice']) : '', 'message' => isset($data['message']) ? sanitize_text_field((string) $data['message']) : '', 'response_code' => isset($data['response_code']) ? absint($data['response_code']) : 0, 'payload_hash' => isset($data['payload_hash']) ? sanitize_text_field((string) $data['payload_hash']) : '', 'setup_pack_id' => isset($data['setup_pack_id']) ? sanitize_key((string) $data['setup_pack_id']) : '', 'plain_key_stored' => false, 'source_of_truth' => 'client_site_plugin_options_after_import', 'transport' => isset($data['transport']) ? sanitize_key((string) $data['transport']) : 'https_post', 'test_mode' => !empty($data['test_mode']), 'temporary_key_id' => isset($data['temporary_key_id']) ? sanitize_key((string) $data['temporary_key_id']) : '', 'temporary_key_consumed'=> !empty($data['temporary_key_consumed']), 'setup_key_restored' => !empty($data['setup_key_restored']), ); update_option(MATEWA_AGENCY_REMOTE_PUSH_LAST_OPTION, $safe, false); return $safe; } } if (!function_exists('matewa_agency_remote_push_clean_client_site_url')) { function matewa_agency_remote_push_clean_client_site_url($value) { $value = trim((string) $value); if ('' === $value) { return ''; } if (!preg_match('#^https?://#i', $value)) { $value = 'https://' . $value; } $value = esc_url_raw($value); if ('' === $value) { return ''; } $parts = wp_parse_url($value); if (!is_array($parts) || empty($parts['host'])) { return ''; } $scheme = isset($parts['scheme']) ? strtolower((string) $parts['scheme']) : 'https'; if ('https' !== $scheme) { return ''; } $host = strtolower((string) $parts['host']); $host = preg_replace('/[^a-z0-9\.\-]/', '', $host); $host = trim((string) $host, '.-'); if ('' === $host) { return ''; } $path = isset($parts['path']) ? (string) $parts['path'] : '/'; $path = '/' . ltrim($path, '/'); $path = preg_replace('#/+#', '/', $path); $path = preg_replace('#/(wp-admin|wp-login\.php|admin\.php|wp-json).*$#i', '/', (string) $path); $path = untrailingslashit($path); if ('' === $path) { $path = '/'; } $port = isset($parts['port']) ? ':' . absint($parts['port']) : ''; return esc_url_raw('https://' . $host . $port . ('/' === $path ? '/' : trailingslashit($path))); } } if (!function_exists('matewa_agency_remote_push_endpoint_url')) { function matewa_agency_remote_push_endpoint_url($client_site_url) { $client_site_url = matewa_agency_remote_push_clean_client_site_url($client_site_url); if ('' === $client_site_url) { return ''; } return esc_url_raw(trailingslashit($client_site_url) . 'wp-json/' . MATEWA_AGENCY_REMOTE_PUSH_REST_NAMESPACE . MATEWA_AGENCY_REMOTE_PUSH_REST_ROUTE); } } if (!function_exists('matewa_agency_remote_push_build_request_body')) { function matewa_agency_remote_push_build_request_body($setup_key, $payload, $pack_id, $client_site_url, $request_id) { $agency_summary = function_exists('matewa_agency_entitlement_get_summary') ? matewa_agency_entitlement_get_summary() : array(); $agency_summary = is_array($agency_summary) ? $agency_summary : array(); return array( 'checkpoint' => 'AGENCYCLIENTFREEMIUMOVERRIDEFIX1', 'request_id' => sanitize_text_field((string) $request_id), 'setup_key' => (string) $setup_key, 'setup_pack_id' => sanitize_key((string) $pack_id), 'client_site_url' => esc_url_raw((string) $client_site_url), 'sent_at' => gmdate('c'), 'agency_source' => array( 'agency_id' => sanitize_key((string) ($agency_summary['agency_id'] ?? '')), 'agency_name' => sanitize_text_field((string) ($agency_summary['agency_name'] ?? '')), 'source_site_url' => esc_url_raw((string) home_url('/')), ), 'payload' => is_array($payload) ? $payload : array(), ); } } if (!function_exists('matewa_agency_remote_push_handle_admin_post')) { function matewa_agency_remote_push_handle_admin_post() { if (!is_admin() || !current_user_can('manage_options')) { wp_die(esc_html__('You do not have permission to push setup packs.', '247chatmate')); } check_admin_referer('matewa_agency_remote_push_setup', 'matewa_agency_remote_push_nonce'); $attempted_at = matewa_agency_remote_push_now(); $request_id = matewa_agency_remote_push_request_id(); $pack_id = isset($_POST['matewa_agency_remote_push_pack_id']) ? sanitize_key((string) wp_unslash($_POST['matewa_agency_remote_push_pack_id'])) : ''; $posted_client_site_url = isset($_POST['matewa_agency_remote_push_client_site_url']) ? esc_url_raw(wp_unslash($_POST['matewa_agency_remote_push_client_site_url'])) : ''; $client_site_url = matewa_agency_remote_push_clean_client_site_url($posted_client_site_url); $setup_key = isset($_POST['matewa_agency_remote_push_setup_key']) ? trim((string) sanitize_text_field(wp_unslash($_POST['matewa_agency_remote_push_setup_key']))) : ''; $endpoint_url = '' !== $client_site_url ? matewa_agency_remote_push_endpoint_url($client_site_url) : ''; $last_base = array( 'attempted_at' => $attempted_at, 'request_id' => $request_id, 'pack_id' => $pack_id, 'client_site_url' => $client_site_url, 'endpoint_url' => $endpoint_url, 'ok' => false, ); if ('' === $pack_id || '' === $setup_key) { matewa_agency_remote_push_store_last(array_merge($last_base, array('notice' => 'remote_push_missing_fields'))); matewa_agency_remote_push_redirect('remote_push_missing_fields'); } if (!function_exists('matewa_agency_hub_is_active') || !matewa_agency_hub_is_active()) { matewa_agency_remote_push_store_last(array_merge($last_base, array('notice' => 'remote_push_agency_required'))); matewa_agency_remote_push_redirect('remote_push_agency_required'); } if (!function_exists('matewa_agency_client_pack_get_record_by_id') || !function_exists('matewa_agency_client_pack_build_export_payload')) { matewa_agency_remote_push_store_last(array_merge($last_base, array('notice' => 'remote_push_payload_failed'))); matewa_agency_remote_push_redirect('remote_push_payload_failed'); } $record = matewa_agency_client_pack_get_record_by_id($pack_id); if (empty($record) || !is_array($record)) { matewa_agency_remote_push_store_last(array_merge($last_base, array('notice' => 'remote_push_pack_not_found'))); matewa_agency_remote_push_redirect('remote_push_pack_not_found'); } if ('' === $client_site_url && !empty($record['client_domain'])) { $client_site_url = matewa_agency_remote_push_clean_client_site_url((string) $record['client_domain']); $endpoint_url = '' !== $client_site_url ? matewa_agency_remote_push_endpoint_url($client_site_url) : ''; $last_base['client_site_url'] = $client_site_url; $last_base['endpoint_url'] = $endpoint_url; } if ('' === $client_site_url || '' === $endpoint_url) { matewa_agency_remote_push_store_last(array_merge($last_base, array('notice' => 'remote_push_invalid_url'))); matewa_agency_remote_push_redirect('remote_push_invalid_url'); } $payload = matewa_agency_client_pack_build_export_payload($record); $json = wp_json_encode($payload, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); if (!is_array($payload) || !is_string($json) || '' === $json) { matewa_agency_remote_push_store_last(array_merge($last_base, array('notice' => 'remote_push_payload_failed'))); matewa_agency_remote_push_redirect('remote_push_payload_failed'); } $payload_hash = function_exists('matewa_agency_client_import_payload_hash') ? matewa_agency_client_import_payload_hash($payload) : hash('sha256', $json); $body = matewa_agency_remote_push_build_request_body($setup_key, $payload, $pack_id, $client_site_url, $request_id); $body_json = wp_json_encode($body, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); if (!is_string($body_json) || '' === $body_json) { matewa_agency_remote_push_store_last(array_merge($last_base, array('notice' => 'remote_push_payload_failed', 'payload_hash' => $payload_hash))); matewa_agency_remote_push_redirect('remote_push_payload_failed'); } $response = wp_remote_post($endpoint_url, array( 'timeout' => 20, 'redirection' => 2, 'reject_unsafe_urls' => true, 'limit_response_size' => 524288, 'user-agent' => '247ChatMate agency remote setup push/' . (defined('MATEWA_VERSION') ? MATEWA_VERSION : '1.0'), 'headers' => array( 'Content-Type' => 'application/json; charset=utf-8', 'Accept' => 'application/json', ), 'body' => $body_json, )); if (is_wp_error($response)) { matewa_agency_remote_push_store_last(array_merge($last_base, array( 'notice' => 'remote_push_request_failed', 'message' => $response->get_error_message(), 'payload_hash' => $payload_hash, ))); matewa_agency_remote_push_redirect('remote_push_request_failed'); } $response_code = (int) wp_remote_retrieve_response_code($response); $response_body = wp_remote_retrieve_body($response); $decoded = is_string($response_body) ? json_decode($response_body, true) : array(); $decoded = is_array($decoded) ? $decoded : array(); $remote_ok = !empty($decoded['ok']); $remote_code = isset($decoded['code']) ? sanitize_key((string) $decoded['code']) : ''; $remote_message = isset($decoded['message']) ? sanitize_text_field((string) $decoded['message']) : ''; if ($response_code >= 200 && $response_code < 300 && $remote_ok) { matewa_agency_remote_push_store_last(array_merge($last_base, array( 'ok' => true, 'notice' => 'remote_push_ok', 'message' => '' !== $remote_message ? $remote_message : 'Client setup imported.', 'response_code' => $response_code, 'payload_hash' => isset($decoded['payload_hash']) ? sanitize_text_field((string) $decoded['payload_hash']) : $payload_hash, 'setup_pack_id' => isset($decoded['setup_pack_id']) ? sanitize_key((string) $decoded['setup_pack_id']) : $pack_id, ))); matewa_agency_remote_push_redirect('remote_push_ok'); } $notice = 'remote_push_client_failed'; if (in_array($response_code, array(401, 403, 410), true) || in_array($remote_code, array('missing_setup_key', 'invalid_setup_key', 'setup_key_inactive_or_expired'), true)) { $notice = 'remote_push_key_consumed'; } elseif ($response_code >= 400 && $response_code < 500) { $notice = 'remote_push_client_rejected'; } matewa_agency_remote_push_store_last(array_merge($last_base, array( 'notice' => $notice, 'message' => '' !== $remote_message ? $remote_message : ('Client response code ' . $response_code), 'response_code' => $response_code, 'payload_hash' => $payload_hash, ))); matewa_agency_remote_push_redirect($notice); } } add_action('admin_post_matewa_agency_remote_push_setup', 'matewa_agency_remote_push_handle_admin_post'); if (!function_exists('matewa_agency_remote_push_render_action_form')) { function matewa_agency_remote_push_render_action_form() { ?>
get_json_params(); if (is_array($params)) { return $params; } } $body = is_object($request) && method_exists($request, 'get_body') ? (string) $request->get_body() : ''; $decoded = '' !== trim($body) ? json_decode($body, true) : array(); return is_array($decoded) ? $decoded : array(); } } if (!function_exists('matewa_agency_remote_push_rest_error')) { function matewa_agency_remote_push_rest_error($code, $message, $status = 400, $extra = array()) { $data = array_merge(array( 'ok' => false, 'code' => sanitize_key((string) $code), 'message' => sanitize_text_field((string) $message), ), is_array($extra) ? $extra : array()); if (class_exists('WP_REST_Response')) { return new WP_REST_Response($data, absint($status)); } return $data; } } if (!function_exists('matewa_agency_remote_push_rest_success')) { function matewa_agency_remote_push_rest_success($data = array(), $status = 200) { $data = array_merge(array('ok' => true), is_array($data) ? $data : array()); if (class_exists('WP_REST_Response')) { return new WP_REST_Response($data, absint($status)); } return $data; } } if (!function_exists('matewa_agency_remote_push_rest_receive')) { function matewa_agency_remote_push_rest_receive($request) { $raw_body = is_object($request) && method_exists($request, 'get_body') ? (string) $request->get_body() : ''; if (strlen($raw_body) > 1048576) { return matewa_agency_remote_push_rest_error('payload_too_large', 'Remote setup payload is too large.', 413); } $params = matewa_agency_remote_push_json_params($request); if (empty($params)) { return matewa_agency_remote_push_rest_error('invalid_json', 'Remote setup request must be valid JSON.', 400); } $setup_key = isset($params['setup_key']) ? trim((string) $params['setup_key']) : ''; if ('' === $setup_key && is_object($request) && method_exists($request, 'get_header')) { $setup_key = trim((string) $request->get_header('x-247chatmate-setup-key')); } if ('' === $setup_key) { return matewa_agency_remote_push_rest_error('missing_setup_key', 'Missing one-time setup key.', 401); } if (!function_exists('matewa_agency_client_setup_key_verify') || !function_exists('matewa_agency_client_setup_key_mark_used')) { return matewa_agency_remote_push_rest_error('setup_key_handler_missing', 'Setup key verification is unavailable on this client site.', 500); } $verification = matewa_agency_client_setup_key_verify($setup_key); $verification = is_array($verification) ? $verification : array('ok' => false, 'reason' => 'verification_failed', 'record' => array()); if (empty($verification['ok'])) { $reason = isset($verification['reason']) ? sanitize_key((string) $verification['reason']) : 'invalid_key'; $status = 'inactive_or_expired' === $reason ? 410 : 401; return matewa_agency_remote_push_rest_error( 'inactive_or_expired' === $reason ? 'setup_key_inactive_or_expired' : 'invalid_setup_key', 'The one-time setup key is invalid, expired, revoked or already used.', $status, array('reason' => $reason) ); } $payload = isset($params['payload']) && is_array($params['payload']) ? $params['payload'] : array(); if (empty($payload)) { return matewa_agency_remote_push_rest_error('missing_payload', 'Missing setup payload.', 400); } if (!function_exists('matewa_agency_client_import_sanitise_payload') || !function_exists('matewa_agency_client_import_apply_payload') || !function_exists('matewa_agency_client_import_payload_hash')) { return matewa_agency_remote_push_rest_error('import_handler_missing', 'Client setup import handler is unavailable on this site.', 500); } $sanitised_result = matewa_agency_client_import_sanitise_payload($payload); $sanitised_result = is_array($sanitised_result) ? $sanitised_result : array('ok' => false, 'errors' => array()); if (empty($sanitised_result['ok'])) { $errors = isset($sanitised_result['errors']) && is_array($sanitised_result['errors']) ? array_values(array_map('sanitize_text_field', $sanitised_result['errors'])) : array(); return matewa_agency_remote_push_rest_error('invalid_payload', 'Setup payload did not match the expected 247ChatMate client setup format.', 400, array('errors' => $errors)); } $sanitised = isset($sanitised_result['sanitised']) && is_array($sanitised_result['sanitised']) ? $sanitised_result['sanitised'] : array(); if (function_exists('matewa_agency_client_import_normalise_entitlement')) { $remote_entitlement = matewa_agency_client_import_normalise_entitlement($payload, array( 'setup_pack_id' => sanitize_key((string) ($sanitised['setup_pack_id'] ?? ($params['setup_pack_id'] ?? ''))), 'agency_id' => sanitize_key((string) (($params['agency_source']['agency_id'] ?? '') ?: ($payload['agency_source']['agency_id'] ?? ''))), 'import_route' => 'remote_push', )); if (is_array($remote_entitlement) && !empty($remote_entitlement['agency_managed'])) { $sanitised['client_entitlement'] = $remote_entitlement; } } $payload_hash = matewa_agency_client_import_payload_hash($sanitised); $pending = array( 'checkpoint' => 'AGENCYCLIENTFREEMIUMOVERRIDEFIX1', 'reviewed_at' => matewa_agency_remote_push_now(), 'import_route' => 'remote_push', 'hash' => $payload_hash, 'sanitised' => $sanitised, 'payload' => $payload, ); $apply_result = matewa_agency_client_import_apply_payload($pending); $apply_result = is_array($apply_result) ? $apply_result : array('ok' => false, 'message' => 'Import failed.'); if (empty($apply_result['ok'])) { return matewa_agency_remote_push_rest_error('import_failed', isset($apply_result['message']) ? (string) $apply_result['message'] : 'Client setup import failed.', 500); } $record = isset($verification['record']) && is_array($verification['record']) ? $verification['record'] : array(); $key_id = isset($record['key_id']) ? sanitize_key((string) $record['key_id']) : ''; matewa_agency_client_setup_key_mark_used($key_id); $request_id = isset($params['request_id']) ? sanitize_text_field((string) $params['request_id']) : ''; $source = isset($params['agency_source']) && is_array($params['agency_source']) ? $params['agency_source'] : array(); $setup_pack_id = sanitize_key((string) ($sanitised['setup_pack_id'] ?? ($params['setup_pack_id'] ?? ''))); $now = matewa_agency_remote_push_now(); $meta = function_exists('matewa_agency_client_import_get_last_meta') ? matewa_agency_client_import_get_last_meta() : array(); $meta = is_array($meta) ? $meta : array(); $meta['checkpoint'] = 'AGENCYCLIENTFREEMIUMOVERRIDEFIX1'; $meta['import_route'] = 'remote_push'; $meta['remote_push_at'] = $now; $meta['remote_request_id'] = $request_id; $meta['setup_key_id'] = $key_id; $meta['setup_key_consumed'] = true; $meta['payload_hash'] = $payload_hash; $meta['setup_pack_id'] = $setup_pack_id; $meta['remote_source_site_url'] = isset($source['source_site_url']) ? esc_url_raw((string) $source['source_site_url']) : ''; $meta['remote_agency_id'] = isset($source['agency_id']) ? sanitize_key((string) $source['agency_id']) : ''; $meta['plain_key_stored'] = false; $meta['source_of_truth'] = 'client_site_plugin_options'; update_option(MATEWA_AGENCY_CLIENT_IMPORTED_META_OPTION, $meta, false); $key_record = function_exists('matewa_agency_client_setup_key_get_record') ? matewa_agency_client_setup_key_get_record() : array(); $key_record = is_array($key_record) ? $key_record : array(); if (!empty($key_record)) { $key_record['checkpoint'] = 'AGENCYCLIENTFREEMIUMOVERRIDEFIX1'; $key_record['remote_push_checkpoint'] = 'AGENCYCLIENTFREEMIUMOVERRIDEFIX1'; $key_record['remote_push_enabled'] = false; $key_record['last_remote_push_at'] = $now; $key_record['last_remote_push_request_id'] = $request_id; $key_record['last_remote_push_payload_hash'] = $payload_hash; $key_record['last_remote_setup_pack_id'] = $setup_pack_id; $key_record['plain_key_stored'] = false; update_option(MATEWA_AGENCY_CLIENT_SETUP_KEY_OPTION, $key_record, false); } return matewa_agency_remote_push_rest_success(array( 'code' => 'remote_push_imported', 'message' => 'Setup imported into this client WordPress site. The client site is now the source of truth.', 'checkpoint' => 'AGENCYCLIENTFREEMIUMOVERRIDEFIX1', 'setup_pack_id' => $setup_pack_id, 'payload_hash' => $payload_hash, 'setup_key_id' => $key_id, 'source_of_truth' => 'client_site_plugin_options', ), 200); } } if (!function_exists('matewa_agency_remote_push_register_rest_route')) { function matewa_agency_remote_push_register_rest_route() { register_rest_route(MATEWA_AGENCY_REMOTE_PUSH_REST_NAMESPACE, MATEWA_AGENCY_REMOTE_PUSH_REST_ROUTE, array( 'methods' => 'POST', 'callback' => 'matewa_agency_remote_push_rest_receive', 'permission_callback' => '__return_true', )); } } add_action('rest_api_init', 'matewa_agency_remote_push_register_rest_route');