0; } } if ( ! function_exists( 'matewa_get_owner_email' ) ) { function matewa_get_owner_email() { return sanitize_email( (string) get_option( 'matewa_marketing_email', '' ) ); } } if ( ! function_exists( 'matewa_has_valid_owner_email' ) ) { function matewa_has_valid_owner_email() { $email = matewa_get_owner_email(); return '' !== $email && is_email( $email ); } } if ( ! function_exists( 'matewa_trial_start' ) ) { function matewa_trial_start( $timestamp = null ) { $timestamp = is_numeric( $timestamp ) ? (int) $timestamp : time(); if ( $timestamp <= 0 ) { $timestamp = time(); } update_option( MATEWA_TRIAL_OPTION, $timestamp, false ); return $timestamp; } } if ( ! function_exists( 'matewa_is_master' ) ) { function matewa_is_master() { $key = matewa_get_license_key(); if ( '' === MATEWA_MASTER_KEY || '' === $key ) { return false; } return hash_equals( MATEWA_MASTER_KEY, $key ); } } if ( ! function_exists( 'matewa_trial_active' ) ) { function matewa_trial_active() { $started = matewa_get_trial_started(); if ( ! $started ) { return false; } return time() < ( $started + ( MATEWA_TRIAL_DAYS * DAY_IN_SECONDS ) ); } } if ( ! function_exists( 'matewa_trial_expired' ) ) { function matewa_trial_expired() { $started = matewa_get_trial_started(); if ( ! $started ) { return false; } return ! matewa_trial_active(); } } /* ====================================================================== LICENCE VALIDATION CACHE / HEARTBEAT HELPERS ====================================================================== */ if ( ! function_exists( 'matewa_get_license_cache_key' ) ) { function matewa_get_license_cache_key( $license_key ) { return 'matewa_ls_' . md5( trim( (string) $license_key ) . home_url() ); } } if ( ! function_exists( 'matewa_get_stored_license_validation_result' ) ) { function matewa_get_stored_license_validation_result( $license_key = '' ) { $stored = get_option( MATEWA_LICENSE_LAST_RESULT_OPTION, array() ); if ( ! is_array( $stored ) || empty( $stored ) ) { return false; } $expected_hash = md5( trim( (string) $license_key ) . home_url() ); $stored_hash = isset( $stored['license_hash'] ) ? (string) $stored['license_hash'] : ''; if ( '' === $stored_hash || ! hash_equals( $expected_hash, $stored_hash ) ) { return false; } $stored['valid'] = ! empty( $stored['valid'] ); if ( $stored['valid'] && ! matewa_license_result_has_instance_binding( $stored ) ) { return false; } $stored['source'] = 'stored'; return $stored; } } if ( ! function_exists( 'matewa_store_license_validation_result' ) ) { function matewa_store_license_validation_result( $result, $license_key = '' ) { if ( ! is_array( $result ) ) { return false; } $instance_id = isset( $result['instance_id'] ) ? sanitize_text_field( (string) $result['instance_id'] ) : ''; $safe_result = array( 'valid' => ! empty( $result['valid'] ), 'code' => sanitize_key( (string) ( $result['code'] ?? 'unknown' ) ), 'message' => sanitize_text_field( (string) ( $result['message'] ?? '' ) ), 'source' => sanitize_key( (string) ( $result['source'] ?? 'remote' ) ), 'checked_at' => isset( $result['checked_at'] ) ? (int) $result['checked_at'] : time(), 'license_hash' => md5( trim( (string) $license_key ) . home_url() ), 'instance_bound' => ! empty( $result['instance_bound'] ), 'instance_id_hash' => '' !== $instance_id ? matewa_license_binding_hash( $instance_id ) : sanitize_text_field( (string) ( $result['instance_id_hash'] ?? '' ) ), ); update_option( MATEWA_LICENSE_LAST_RESULT_OPTION, $safe_result, false ); return true; } } if ( ! function_exists( 'matewa_store_license_validation_error' ) ) { function matewa_store_license_validation_error( $result, $license_key = '' ) { if ( ! is_array( $result ) ) { return false; } $safe_error = array( 'valid' => false, 'code' => sanitize_key( (string) ( $result['code'] ?? 'unknown' ) ), 'message' => sanitize_text_field( (string) ( $result['message'] ?? '' ) ), 'source' => sanitize_key( (string) ( $result['source'] ?? 'remote' ) ), 'checked_at' => isset( $result['checked_at'] ) ? (int) $result['checked_at'] : time(), 'license_hash' => md5( trim( (string) $license_key ) . home_url() ), ); update_option( MATEWA_LICENSE_LAST_ERROR_OPTION, $safe_error, false ); return true; } } if ( ! function_exists( 'matewa_license_binding_hash' ) ) { function matewa_license_binding_hash( $value ) { $value = trim( (string) $value ); if ( '' === $value ) { return ''; } if ( function_exists( 'wp_salt' ) ) { return hash_hmac( 'sha256', $value, wp_salt( 'auth' ) ); } return hash( 'sha256', $value ); } } if ( ! function_exists( 'matewa_license_site_domain' ) ) { function matewa_license_site_domain() { $host = wp_parse_url( home_url( '/' ), PHP_URL_HOST ); $host = is_string( $host ) ? strtolower( trim( $host ) ) : ''; $host = preg_replace( '/[^a-z0-9\.\-]/', '', $host ); return '' !== $host ? $host : 'wordpress-site'; } } if ( ! function_exists( 'matewa_license_instance_name' ) ) { function matewa_license_instance_name() { $domain = matewa_license_site_domain(); $name = '247ChatMate - ' . $domain; return substr( sanitize_text_field( $name ), 0, 90 ); } } if ( ! function_exists( 'matewa_get_license_instance_record' ) ) { function matewa_get_license_instance_record( $license_key = '' ) { $license_key = trim( (string) $license_key ); $record = get_option( MATEWA_LICENSE_INSTANCE_OPTION, array() ); if ( ! is_array( $record ) || empty( $record ) ) { return array(); } $expected_license_hash = matewa_license_binding_hash( $license_key ); $expected_domain_hash = matewa_license_binding_hash( matewa_license_site_domain() ); $stored_license_hash = isset( $record['license_hash'] ) ? (string) $record['license_hash'] : ''; $stored_domain_hash = isset( $record['domain_hash'] ) ? (string) $record['domain_hash'] : ''; if ( '' === $expected_license_hash || '' === $stored_license_hash || ! hash_equals( $expected_license_hash, $stored_license_hash ) ) { return array(); } if ( '' === $expected_domain_hash || '' === $stored_domain_hash || ! hash_equals( $expected_domain_hash, $stored_domain_hash ) ) { return array(); } return $record; } } if ( ! function_exists( 'matewa_get_license_instance_id' ) ) { function matewa_get_license_instance_id( $license_key = '' ) { $record = matewa_get_license_instance_record( $license_key ); return isset( $record['instance_id'] ) ? sanitize_text_field( (string) $record['instance_id'] ) : ''; } } if ( ! function_exists( 'matewa_store_license_instance_binding' ) ) { function matewa_store_license_instance_binding( $license_key, $instance_id, $instance_name = '' ) { $license_key = trim( (string) $license_key ); $instance_id = sanitize_text_field( (string) $instance_id ); $instance_name = sanitize_text_field( (string) $instance_name ); if ( '' === $license_key || '' === $instance_id ) { return false; } $record = array( 'license_hash' => matewa_license_binding_hash( $license_key ), 'domain_hash' => matewa_license_binding_hash( matewa_license_site_domain() ), 'instance_id' => $instance_id, 'instance_name' => '' !== $instance_name ? $instance_name : matewa_license_instance_name(), 'created_at' => time(), ); update_option( MATEWA_LICENSE_INSTANCE_OPTION, $record, false ); return true; } } if ( ! function_exists( 'matewa_license_result_has_instance_binding' ) ) { function matewa_license_result_has_instance_binding( $result ) { return is_array( $result ) && ! empty( $result['instance_bound'] ) && ! empty( $result['instance_id_hash'] ); } } if ( ! function_exists( 'matewa_activate_license_instance' ) ) { function matewa_activate_license_instance( $license_key ) { $license_key = trim( (string) $license_key ); $result = array( 'valid' => false, 'code' => 'activation_failed', 'message' => 'Licence activation failed. Check the saved licence key and try again.', 'source' => 'remote_activation', 'checked_at' => time(), 'instance_bound' => false, ); if ( '' === $license_key ) { $result['code'] = 'missing_key'; $result['message'] = 'No licence key saved.'; return $result; } $response = wp_remote_post( 'https://api.lemonsqueezy.com/v1/licenses/activate', array( 'timeout' => 8, 'headers' => array( 'Accept' => 'application/json', 'Content-Type' => 'application/x-www-form-urlencoded', ), 'body' => array( 'license_key' => $license_key, 'instance_name' => matewa_license_instance_name(), ), ) ); if ( is_wp_error( $response ) ) { $result['code'] = 'activation_request_error'; $result['message'] = 'Licence activation request failed. Try again later.'; return $result; } $status_code = (int) wp_remote_retrieve_response_code( $response ); $body = json_decode( wp_remote_retrieve_body( $response ), true ); if ( ! is_array( $body ) ) { $result['code'] = 'activation_invalid_response'; $result['message'] = 'Licence activation server returned an invalid response.'; return $result; } $error_message = ''; if ( ! empty( $body['error'] ) && is_string( $body['error'] ) ) { $error_message = sanitize_text_field( $body['error'] ); } if ( 200 !== $status_code || empty( $body['activated'] ) ) { $result['code'] = 'activation_failed'; $result['message'] = '' !== $error_message ? $error_message : 'Licence activation failed. This key may already be in use or over its activation limit.'; return $result; } $instance_id = ''; if ( isset( $body['instance']['id'] ) && is_string( $body['instance']['id'] ) ) { $instance_id = sanitize_text_field( $body['instance']['id'] ); } if ( '' === $instance_id ) { $result['code'] = 'activation_missing_instance'; $result['message'] = 'Licence activated, but no site instance was returned. Pro access has not been granted.'; return $result; } $instance_name = isset( $body['instance']['name'] ) && is_string( $body['instance']['name'] ) ? sanitize_text_field( $body['instance']['name'] ) : matewa_license_instance_name(); matewa_store_license_instance_binding( $license_key, $instance_id, $instance_name ); $result['valid'] = true; $result['code'] = 'activated'; $result['message'] = 'Licence activated for this website.'; $result['instance_bound'] = true; $result['instance_id_hash'] = matewa_license_binding_hash( $instance_id ); return $result; } } if ( ! function_exists( 'matewa_clear_paid_license_validation_state' ) ) { function matewa_clear_paid_license_validation_state( $old_license_key = '', $new_license_key = '' ) { $old_license_key = trim( (string) $old_license_key ); $new_license_key = trim( (string) $new_license_key ); if ( $old_license_key === $new_license_key ) { return false; } // PAIDLICENCECLEARSYNC3F5E3: // A changed or cleared saved licence key must not keep an old paid // LemonSqueezy validation snapshot alive. This keeps Pro from remaining // active after the paid key is removed or replaced. foreach ( array_unique( array_filter( array( $old_license_key, $new_license_key ) ) ) as $license_key ) { if ( function_exists( 'delete_transient' ) ) { delete_transient( matewa_get_license_cache_key( $license_key ) ); } } delete_option( MATEWA_LICENSE_LAST_RESULT_OPTION ); delete_option( MATEWA_LICENSE_LAST_ERROR_OPTION ); /* * PAIDLICENCEINSTANCERECOVERY1: * Do not delete the saved LemonSqueezy instance binding merely because * the user temporarily replaces or clears the licence key. The binding * itself is tied to both the licence-key hash and this site's domain hash, * so it is ignored for any different key/site. Preserving it lets the * original valid key recover its existing single-site activation instead * of incorrectly trying to consume a second activation. */ return true; } } if ( ! function_exists( 'matewa_sync_paid_license_validation_state_on_option_update' ) ) { function matewa_sync_paid_license_validation_state_on_option_update( $old_value, $value, $option ) { if ( MATEWA_LICENSE_OPTION !== $option ) { return; } matewa_clear_paid_license_validation_state( $old_value, $value ); } } add_action( 'update_option_' . MATEWA_LICENSE_OPTION, 'matewa_sync_paid_license_validation_state_on_option_update', 10, 3 ); /* ====================================================================== LEMON SQUEEZY VALIDATION Minimal diagnostic layer for admin feedback ====================================================================== */ if ( ! function_exists( 'matewa_get_license_validation_result' ) ) { function matewa_get_license_validation_result( $license_key = '', $force_remote = false ) { static $request_cache = array(); $license_key = trim( (string) $license_key ); $result = array( 'valid' => false, 'code' => 'unknown', 'message' => '', 'source' => 'runtime', 'checked_at' => time(), ); if ( '' === $license_key ) { $result['code'] = 'missing_key'; $result['message'] = 'No licence key saved.'; return $result; } if ( matewa_license_key_is_founder_beta_access( $license_key ) ) { // PAIDBETASEPARATIONSAFETY3F5E3: // Private Founder Beta keys must not be sent to LemonSqueezy or // stored as failed paid licence checks. Founder Beta remains a // separate entitlement source below paid LemonSqueezy validation. $result['code'] = 'founder_beta_key'; $result['message'] = 'Private Founder Beta access key saved. LemonSqueezy validation skipped.'; $result['source'] = 'local'; return $result; } $cache_key = matewa_get_license_cache_key( $license_key ); if ( $force_remote ) { delete_transient( $cache_key ); } elseif ( isset( $request_cache[ $cache_key ] ) && is_array( $request_cache[ $cache_key ] ) ) { return $request_cache[ $cache_key ]; } $cached = $force_remote ? false : get_transient( $cache_key ); if ( is_array( $cached ) ) { $cached = array_merge( $result, $cached ); $cached['valid'] = ! empty( $cached['valid'] ); if ( $cached['valid'] && ! matewa_license_result_has_instance_binding( $cached ) ) { $cached['valid'] = false; $cached['code'] = 'instance_not_bound'; $cached['message'] = 'Licence must be activated for this website before Pro access is granted.'; } $cached['source'] = 'cache'; $request_cache[ $cache_key ] = $cached; return $cached; } if ( false !== $cached ) { $result['valid'] = (bool) $cached; $result['code'] = $result['valid'] ? 'valid_cached' : 'invalid_cached'; $result['message'] = $result['valid'] ? 'Licence validation passed from cache.' : 'Licence validation failed from cache.'; $result['source'] = 'cache'; $request_cache[ $cache_key ] = $result; return $result; } if ( ! $force_remote && matewa_should_defer_remote_license_validation() ) { $stored = matewa_get_stored_license_validation_result( $license_key ); if ( is_array( $stored ) ) { $request_cache[ $cache_key ] = $stored; return $stored; } $result['code'] = 'deferred_admin_request'; $result['message'] = 'Licence validation deferred during this admin request.'; $result['source'] = 'deferred'; $request_cache[ $cache_key ] = $result; return $result; } $instance_id = matewa_get_license_instance_id( $license_key ); if ( $force_remote && '' === $instance_id ) { $activation_result = matewa_activate_license_instance( $license_key ); if ( empty( $activation_result['valid'] ) ) { set_transient( $cache_key, $activation_result, HOUR_IN_SECONDS ); matewa_store_license_validation_result( $activation_result, $license_key ); $request_cache[ $cache_key ] = $activation_result; return $activation_result; } $instance_id = matewa_get_license_instance_id( $license_key ); } if ( '' === $instance_id ) { $result['valid'] = false; $result['code'] = 'instance_not_bound'; $result['message'] = 'Licence must be activated for this website before Pro access is granted.'; $result['source'] = 'local'; $result['instance_bound'] = false; $request_cache[ $cache_key ] = $result; return $result; } $validation_body = array( 'license_key' => $license_key, 'instance_id' => $instance_id, ); $response = wp_remote_post( 'https://api.lemonsqueezy.com/v1/licenses/validate', array( 'timeout' => 5, 'headers' => array( 'Accept' => 'application/json', 'Content-Type' => 'application/x-www-form-urlencoded', ), 'body' => $validation_body, ) ); if ( is_wp_error( $response ) ) { $result['code'] = 'request_error'; $result['message'] = 'Licence validation request failed.'; $result['source'] = 'remote'; matewa_store_license_validation_error( $result, $license_key ); $request_cache[ $cache_key ] = $result; return $result; } $status_code = (int) wp_remote_retrieve_response_code( $response ); $body_raw = wp_remote_retrieve_body( $response ); $body = json_decode( $body_raw, true ); if ( ! is_array( $body ) ) { $result['code'] = 'invalid_response'; $result['message'] = 'Licence server returned an invalid response.'; $result['source'] = 'remote'; matewa_store_license_validation_error( $result, $license_key ); $request_cache[ $cache_key ] = $result; return $result; } // LICENCEINVALIDRESPONSELOCK1: // LemonSqueezy returns expected 4xx JSON responses for invalid, missing, // or malformed licence keys. Those are not server failures; store them // as a clean invalid licence result so the UI and frontend can fall back // safely instead of showing "unexpected response". if ( in_array( $status_code, array( 400, 404, 422 ), true ) ) { $error_message = ''; if ( ! empty( $body['error'] ) && is_string( $body['error'] ) ) { $error_message = sanitize_text_field( $body['error'] ); } $result['valid'] = false; $result['code'] = 'invalid'; $result['message'] = '' !== $error_message ? $error_message : 'Licence validation failed. Check the saved licence key and try again.'; $result['source'] = 'remote'; set_transient( $cache_key, $result, HOUR_IN_SECONDS ); matewa_store_license_validation_result( $result, $license_key ); $request_cache[ $cache_key ] = $result; return $result; } if ( 200 !== $status_code ) { $result['code'] = 'invalid_response'; $result['message'] = 'Licence server returned an invalid response.'; $result['source'] = 'remote'; matewa_store_license_validation_error( $result, $license_key ); $request_cache[ $cache_key ] = $result; return $result; } $license_status = ''; if ( isset( $body['license_key']['status'] ) && is_string( $body['license_key']['status'] ) ) { $license_status = sanitize_key( $body['license_key']['status'] ); } $license_meta = isset( $body['meta'] ) && is_array( $body['meta'] ) ? $body['meta'] : array(); $response_instance_id = ''; if ( isset( $body['instance']['id'] ) && is_string( $body['instance']['id'] ) ) { $response_instance_id = sanitize_text_field( $body['instance']['id'] ); } $valid = ! empty( $body['valid'] ); $valid_paid_statuses = array( '', 'active', 'inactive' ); $blocked_paid_statuses = array( 'cancelled', 'canceled', 'refunded', 'expired', 'disabled', 'deactivated' ); // PAIDLICENCECANCELLATIONREFUNDEXPIREDAUDIT3F5E11: // LemonSqueezy remains the paid licence authority, but local entitlement // must fail closed for explicit cancellation/refund/expiry/disabled style // lifecycle statuses and any unrecognised status that arrives with a true // `valid` flag. The paid registry mirror is not consulted here. if ( in_array( $license_status, $blocked_paid_statuses, true ) || ! in_array( $license_status, $valid_paid_statuses, true ) ) { $valid = false; } /* * PAIDTERMINALSTATUSFIX1: * LemonSqueezy's licence validation can remain valid while the linked * subscription lifecycle has already reached an authoritative terminal * state. A secret-free, site-proof server check is therefore used only * to REMOVE stale paid access. It can never grant Pro. */ if ( $valid && function_exists( 'matewa_terminal_status_check' ) ) { $terminal_status = matewa_terminal_status_check( $license_meta ); if ( ! empty( $terminal_status['ok'] ) && ! empty( $terminal_status['terminal'] ) ) { $valid = false; $terminal_code = sanitize_key( (string) ( $terminal_status['subscription_status'] ?? '' ) ); if ( '' === $terminal_code ) { $terminal_code = sanitize_key( (string) ( $terminal_status['stored_status'] ?? '' ) ); } if ( '' === $terminal_code ) { $terminal_code = 'expired'; } $license_status = $terminal_code; } } if ( $valid && '' === $response_instance_id ) { $valid = false; $license_status = 'instance_not_validated'; } if ( $valid && ! hash_equals( $instance_id, $response_instance_id ) ) { $valid = false; $license_status = 'instance_mismatch'; } $result['valid'] = $valid; $result['code'] = $valid ? 'valid' : ( '' !== $license_status ? $license_status : 'invalid' ); $result['source'] = 'remote'; $result['instance_bound'] = $valid && '' !== $response_instance_id; $result['instance_id'] = $response_instance_id; $result['instance_id_hash'] = '' !== $response_instance_id ? matewa_license_binding_hash( $response_instance_id ) : ''; if ( $valid ) { $result['message'] = 'Licence validation passed.'; } elseif ( ! empty( $body['error'] ) && is_string( $body['error'] ) ) { $result['message'] = sanitize_text_field( $body['error'] ); } elseif ( 'cancelled' === $license_status || 'canceled' === $license_status ) { $result['message'] = 'Licence key has been cancelled.'; } elseif ( 'refunded' === $license_status ) { $result['message'] = 'Licence key has been refunded.'; } elseif ( 'expired' === $license_status ) { $result['message'] = 'Licence key has expired.'; } elseif ( 'disabled' === $license_status ) { $result['message'] = 'Licence key has been disabled.'; } elseif ( 'deactivated' === $license_status ) { $result['message'] = 'Licence key has been deactivated.'; } elseif ( 'instance_not_validated' === $license_status ) { $result['message'] = 'Licence key is valid, but this website instance was not validated. Pro access has not been granted.'; } elseif ( 'instance_mismatch' === $license_status ) { $result['message'] = 'Licence key is linked to a different website instance. Pro access has not been granted.'; } else { $result['message'] = 'Licence validation failed.'; } set_transient( $cache_key, $result, MATEWA_LS_CACHE_TTL ); matewa_store_license_validation_result( $result, $license_key ); $request_cache[ $cache_key ] = $result; return $result; } } if ( ! function_exists( 'matewa_validate_license_ls' ) ) { function matewa_validate_license_ls( $license_key ) { $result = matewa_get_license_validation_result( $license_key ); return ! empty( $result['valid'] ); } } /* ====================================================================== CONTROLLED DAILY LICENCE HEARTBEAT ====================================================================== */ if ( ! function_exists( 'matewa_run_daily_license_check' ) ) { function matewa_run_daily_license_check() { $license_key = matewa_get_license_key(); if ( '' === $license_key || matewa_is_master() ) { return false; } if ( matewa_license_key_is_founder_beta_access( $license_key ) ) { return false; } return matewa_get_license_validation_result( $license_key, true ); } } if ( ! function_exists( 'matewa_schedule_daily_license_check' ) ) { function matewa_schedule_daily_license_check() { if ( ! function_exists( 'wp_next_scheduled' ) || ! function_exists( 'wp_schedule_event' ) ) { return; } if ( wp_next_scheduled( MATEWA_LICENSE_DAILY_CHECK_HOOK ) ) { return; } wp_schedule_event( time() + HOUR_IN_SECONDS, 'daily', MATEWA_LICENSE_DAILY_CHECK_HOOK ); } } if ( ! function_exists( 'matewa_clear_daily_license_check' ) ) { function matewa_clear_daily_license_check() { if ( ! function_exists( 'wp_next_scheduled' ) || ! function_exists( 'wp_unschedule_event' ) ) { return; } $timestamp = wp_next_scheduled( MATEWA_LICENSE_DAILY_CHECK_HOOK ); if ( $timestamp ) { wp_unschedule_event( $timestamp, MATEWA_LICENSE_DAILY_CHECK_HOOK ); } } } add_action( MATEWA_LICENSE_DAILY_CHECK_HOOK, 'matewa_run_daily_license_check' ); add_action( 'init', 'matewa_schedule_daily_license_check' ); if ( ! defined( 'MATEWA_LICENSE_ADMIN_REFRESH_OPTION' ) ) { define( 'MATEWA_LICENSE_ADMIN_REFRESH_OPTION', 'matewa_license_admin_refresh_last' ); } if ( ! defined( 'MATEWA_LICENSE_ADMIN_REFRESH_INTERVAL' ) ) { define( 'MATEWA_LICENSE_ADMIN_REFRESH_INTERVAL', 15 * MINUTE_IN_SECONDS ); } if ( ! function_exists( 'matewa_maybe_run_admin_license_refresh' ) ) { function matewa_maybe_run_admin_license_refresh() { if ( ! is_admin() || ! current_user_can( 'manage_options' ) ) { return; } if ( function_exists( 'wp_doing_ajax' ) && wp_doing_ajax() ) { return; } $license_key = matewa_get_license_key(); if ( '' === $license_key || matewa_is_master() || matewa_license_key_is_founder_beta_access( $license_key ) ) { return; } $license_is_agency_access = function_exists( 'matewa_agency_registry_is_access_code' ) ? matewa_agency_registry_is_access_code( $license_key ) : false; if ( $license_is_agency_access ) { return; } $last = (int) get_option( MATEWA_LICENSE_ADMIN_REFRESH_OPTION, 0 ); if ( $last > 0 && ( time() - $last ) < (int) MATEWA_LICENSE_ADMIN_REFRESH_INTERVAL ) { return; } update_option( MATEWA_LICENSE_ADMIN_REFRESH_OPTION, time(), false ); matewa_get_license_validation_result( $license_key, true ); } } add_action( 'admin_init', 'matewa_maybe_run_admin_license_refresh', 20 ); if ( defined( 'MATEWA_MAIN_FILE' ) && function_exists( 'register_deactivation_hook' ) ) { register_deactivation_hook( MATEWA_MAIN_FILE, 'matewa_clear_daily_license_check' ); } /* ====================================================================== DEVELOPMENT OVERRIDE (SAFE) ====================================================================== */ if ( ! defined( 'MATEWA_DEV_FORCE_PRO' ) ) { define( 'MATEWA_DEV_FORCE_PRO', false ); } if ( ! function_exists( 'matewa_get_entitlement_state' ) ) { function matewa_get_entitlement_state() { if ( defined( 'MATEWA_DEV_FORCE_PRO' ) && true === MATEWA_DEV_FORCE_PRO ) { return MATEWA_MODE_PRO; } if ( matewa_is_master() ) { return MATEWA_MODE_MASTER; } $license = matewa_get_license_key(); $license_is_agency_access = function_exists( 'matewa_agency_registry_is_access_code' ) ? matewa_agency_registry_is_access_code( $license ) : false; // AGENCYFREEKEYREGISTRYLOCK1: // Free agency own-site access is validated by the 247ChatMate registry. // It can unlock Pro/Agency Hub eligibility for the agency's own website, // but it must never be sent through LemonSqueezy or grant client seats. if ( $license_is_agency_access && function_exists( 'matewa_agency_own_site_access_is_active' ) && matewa_agency_own_site_access_is_active() ) { return MATEWA_MODE_PRO; } // LICENCEPRECEDENCEFIX1: // An explicitly saved access/licence code is authoritative. A stale or // unrelated agency-client entitlement must not keep this installation in // Pro after the user replaces a valid standalone code with an invalid one. // // Valid explicit codes still unlock Pro through their own validation path. // If an explicit code is invalid, entitlement falls through to Trial/Free. if ( '' !== $license ) { if ( ! $license_is_agency_access && ! matewa_license_key_is_founder_beta_access( $license ) && matewa_validate_license_ls( $license ) ) { return MATEWA_MODE_PRO; } if ( matewa_license_key_is_founder_beta_access( $license ) && function_exists( 'matewa_founder_beta_is_active' ) && matewa_founder_beta_is_active() ) { return MATEWA_MODE_PRO; } } else { // Agency-managed client access is a separate entitlement route and is // consulted only when no standalone access/licence code is saved. if ( function_exists( 'matewa_agency_client_entitlement_is_active' ) && matewa_agency_client_entitlement_is_active() ) { return MATEWA_MODE_PRO; } } if ( matewa_distribution_has_pro_trial() ) { if ( matewa_trial_active() ) { return MATEWA_MODE_TRIAL; } if ( matewa_trial_expired() ) { return MATEWA_MODE_TRIAL_EXPIRED; } } return MATEWA_MODE_FREE; } } if ( ! function_exists( 'matewa_license_status' ) ) { function matewa_license_status() { return matewa_get_entitlement_state(); } } if ( ! function_exists( 'matewa_is_pro' ) ) { function matewa_is_pro() { $state = matewa_get_entitlement_state(); return in_array( $state, array( MATEWA_MODE_PRO, MATEWA_MODE_MASTER ), true ); } } if ( ! function_exists( 'matewa_get_license_diagnostics' ) ) { function matewa_get_license_diagnostics() { $state = matewa_get_entitlement_state(); $license_key = matewa_get_license_key(); $data = array( 'entitlement_state' => $state, 'has_license_key' => ( '' !== $license_key ), 'is_master' => false, 'is_dev_forced' => false, 'license' => array( 'valid' => false, 'code' => 'not_checked', 'message' => 'Licence validation not checked.', 'source' => 'runtime', 'checked_at' => time(), ), 'trial' => array( 'has_started' => matewa_trial_has_started(), 'started_at' => matewa_get_trial_started(), 'active' => matewa_trial_active(), 'expired' => matewa_trial_expired(), ), ); if ( defined( 'MATEWA_DEV_FORCE_PRO' ) && true === MATEWA_DEV_FORCE_PRO ) { $data['is_dev_forced'] = true; $data['license'] = array( 'valid' => true, 'code' => 'dev_force_pro', 'message' => 'Pro is active via development override.', 'source' => 'constant', 'checked_at' => time(), ); return $data; } if ( matewa_is_master() ) { $data['is_master'] = true; $data['license'] = array( 'valid' => true, 'code' => 'master_key', 'message' => 'Master access is active.', 'source' => 'constant', 'checked_at' => time(), ); return $data; } $paid_license_result = array(); $license_key_is_agency = function_exists( 'matewa_agency_registry_is_access_code' ) ? matewa_agency_registry_is_access_code( $license_key ) : false; // AGENCYFREEKEYREGISTRYLOCK1: // Agency own-site access is a registry entitlement, not a LemonSqueezy // paid licence. It unlocks Pro on the agency's own website and prepares // Agency Hub eligibility, but reports zero client seats at this checkpoint. if ( $license_key_is_agency ) { $agency_summary = function_exists( 'matewa_agency_entitlement_get_summary' ) ? matewa_agency_entitlement_get_summary() : array(); $data['agency'] = $agency_summary; if ( function_exists( 'matewa_agency_own_site_access_is_active' ) && matewa_agency_own_site_access_is_active() ) { $data['license'] = array( 'valid' => true, 'code' => 'agency_own_site_active', 'message' => 'Agency own-site access is active from the registry response.', 'source' => 'agency_registry', 'checked_at' => isset( $agency_summary['checked_at'] ) ? (int) $agency_summary['checked_at'] : time(), 'agency_id' => isset( $agency_summary['agency_id'] ) ? sanitize_key( (string) $agency_summary['agency_id'] ) : '', 'agency_name' => isset( $agency_summary['agency_name'] ) ? sanitize_text_field( (string) $agency_summary['agency_name'] ) : '', 'client_seats'=> 0, ); return $data; } $data['license'] = array( 'valid' => false, 'code' => isset( $agency_summary['code'] ) ? sanitize_key( (string) $agency_summary['code'] ) : 'agency_access_not_active', 'message' => isset( $agency_summary['message'] ) && '' !== $agency_summary['message'] ? sanitize_text_field( (string) $agency_summary['message'] ) : 'Agency own-site access is not active.', 'source' => 'agency_registry', 'checked_at' => isset( $agency_summary['checked_at'] ) ? (int) $agency_summary['checked_at'] : time(), ); } elseif ( '' !== $license_key && ! matewa_license_key_is_founder_beta_access( $license_key ) ) { // PAIDLICENCEPLANNING3F5E1: // Preserve the existing LemonSqueezy validation path, but make admin // diagnostics follow the real entitlement priority. A valid paid // licence should be shown as the active source before Founder Beta. $paid_license_result = matewa_get_license_validation_result( $license_key ); if ( ! empty( $paid_license_result['valid'] ) ) { $data['license'] = $paid_license_result; return $data; } } elseif ( '' !== $license_key ) { $paid_license_result = array( 'valid' => false, 'code' => 'founder_beta_key', 'message' => 'Private Founder Beta access key saved. LemonSqueezy validation skipped.', 'source' => 'local', 'checked_at' => time(), ); } if ( function_exists( 'matewa_founder_beta_is_active' ) && matewa_founder_beta_is_active() ) { $founder_beta = function_exists( 'matewa_founder_beta_get_last_result' ) ? matewa_founder_beta_get_last_result() : array(); $data['license'] = array( 'valid' => true, 'code' => 'founder_beta_active', 'message' => 'Founder Beta access is active from the registry response.', 'source' => 'founder_beta_registry', 'checked_at' => isset( $founder_beta['checked_at'] ) ? (int) $founder_beta['checked_at'] : time(), 'beta_start' => isset( $founder_beta['beta_start'] ) ? sanitize_text_field( (string) $founder_beta['beta_start'] ) : '', 'beta_expiry' => isset( $founder_beta['beta_expiry'] ) ? sanitize_text_field( (string) $founder_beta['beta_expiry'] ) : '', ); return $data; } if ( '' !== $license_key ) { $data['license'] = $paid_license_result; } else { $data['license'] = array( 'valid' => false, 'code' => 'missing_key', 'message' => 'No licence key saved.', 'source' => 'runtime', 'checked_at' => time(), ); } return $data; } } if ( ! function_exists( 'matewa_can_start_trial' ) ) { function matewa_can_start_trial() { if ( ! matewa_distribution_has_pro_trial() ) { return false; } $state = matewa_get_entitlement_state(); if ( MATEWA_MODE_FREE !== $state ) { return false; } return matewa_has_valid_owner_email(); } } if ( ! function_exists( 'matewa_start_trial_if_allowed' ) ) { function matewa_start_trial_if_allowed() { $state = matewa_get_entitlement_state(); $result = array( 'ok' => false, 'code' => 'unknown', 'message' => '', 'state' => $state, ); if ( ! matewa_distribution_has_pro_trial() ) { $result['code'] = 'trial_not_available'; $result['message'] = 'This distribution does not include a Pro trial. Add a valid licence to activate Pro.'; return $result; } /* * PROTRIALREGISTRYLIVEFILES1: * Paid / master entitlement must not be blocked by registry availability. * Registry validation only runs before a new local trial is started. */ if ( MATEWA_MODE_PRO === $state || MATEWA_MODE_MASTER === $state ) { $result['code'] = 'already_pro'; $result['message'] = 'Pro is already active.'; $result['state'] = $state; return $result; } if ( MATEWA_MODE_TRIAL === $state ) { $result['code'] = 'already_trial'; $result['message'] = 'Trial is already active.'; $result['state'] = $state; return $result; } if ( MATEWA_MODE_TRIAL_EXPIRED === $state ) { $result['code'] = 'trial_expired'; $result['message'] = 'This trial has already expired.'; $result['state'] = $state; return $result; } if ( ! matewa_has_valid_owner_email() ) { $result['code'] = 'missing_email'; $result['message'] = 'Add a valid marketing email before starting the trial.'; return $result; } if ( ! function_exists( 'matewa_pro_trial_registry_start_trial' ) ) { $result['code'] = 'registry_client_missing'; $result['message'] = 'Pro trial validation is not available in this build. Trial start has been blocked safely.'; return $result; } $registry_result = matewa_pro_trial_registry_start_trial(); if ( ! is_array( $registry_result ) || empty( $registry_result['ok'] ) ) { $result['code'] = sanitize_key( (string) ( $registry_result['code'] ?? 'registry_unavailable' ) ); $result['message'] = sanitize_text_field( (string) ( $registry_result['message'] ?? 'Pro trial validation failed. Trial start has been blocked safely.' ) ); return $result; } $started_at = isset( $registry_result['trial_started_at'] ) ? (int) $registry_result['trial_started_at'] : 0; if ( $started_at <= 0 ) { $started_at = time(); } matewa_trial_start( $started_at ); $result['ok'] = true; $result['code'] = sanitize_key( (string) ( $registry_result['code'] ?? 'trial_started' ) ); $result['message'] = sanitize_text_field( (string) ( $registry_result['message'] ?? 'Your 28-day trial has started.' ) ); $result['state'] = MATEWA_MODE_TRIAL; $result['trial_reference'] = sanitize_text_field( (string) ( $registry_result['trial_reference'] ?? '' ) ); return $result; } }