isset( $resp['status'] ) ? sanitize_text_field( (string) $resp['status'] ) : '', 'source' => $source, 'expires_at' => isset( $resp['expires_at'] ) ? sanitize_text_field( (string) $resp['expires_at'] ) : '', 'domain_limit' => $domain_limit, 'domains_used' => $domains_used, 'domain' => isset( $resp['domain'] ) ? sanitize_text_field( (string) $resp['domain'] ) : '', 'email' => isset( $resp['email'] ) ? sanitize_text_field( (string) $resp['email'] ) : '', 'checked_at' => time(), ]; update_option( 'bongoto_license_meta', $meta ); } /** * Direct-sale license activation (your own server). * Endpoint expects: { email, key, site_url, instance, version } */ /** * Purchase Code verification (your own server). * Endpoint expects: { purchase_key, site_url, version, product_id, product_name } */ function bongoto_shop_verify_purchase_code( string $purchase_key ) { $purchase_key = sanitize_text_field( $purchase_key ); $resp = wp_remote_post( BONGOTO_LICENSE_API_URL, [ 'timeout' => 20, 'headers' => [ 'Content-Type' => 'application/json', ], 'body' => wp_json_encode([ 'product_id' => 'bongoto-shop', 'product_name' => 'Bongoto Shop', 'purchase_key' => $purchase_key, 'site_url' => rtrim( home_url(), '/' ), 'version' => wp_get_theme()->get( 'Version' ), ]), ] ); if ( is_wp_error( $resp ) ) { return $resp; } $code = (int) wp_remote_retrieve_response_code( $resp ); $body = (string) wp_remote_retrieve_body( $resp ); $data = json_decode( $body, true ); if ( 200 === $code && is_array( $data ) ) { return $data; } $msg = function_exists( 'bongoto_shop_license_api_error_message' ) ? bongoto_shop_license_api_error_message( $resp ) : ''; if ( '' === $msg ) { $msg = bongoto_shop_license_extract_error( $body ); } if ( '' === $msg ) { $msg = __( 'Purchase code verification failed.', 'bongoto-shop' ); } return new WP_Error( 'bongoto_purchase_verify_failed', $msg, [ 'http' => $code, 'body' => $body ] ); } /** * Validate already-saved purchase code (lightweight re-check). * Used for the Setup / License page alerts and to enable/disable Import. */ function bongoto_shop_validate_purchase_code( string $purchase_key ) { $purchase_key = sanitize_text_field( $purchase_key ); if ( '' === $purchase_key ) { return new WP_Error( 'bongoto_purchase_key_empty', 'Purchase code is empty.' ); } // By default, validate uses the same endpoint as verify. $url = (string) BONGOTO_LICENSE_API_VALIDATE_URL; if ( '' === $url ) { $url = (string) BONGOTO_LICENSE_API_URL; } $resp = wp_remote_post( $url, [ 'timeout' => 20, 'headers' => [ 'Content-Type' => 'application/json' ], 'body' => wp_json_encode([ 'product_id' => 'bongoto-shop', 'product_name' => 'Bongoto Shop', 'purchase_key' => $purchase_key, 'site_url' => rtrim( home_url(), '/' ), 'version' => wp_get_theme()->get( 'Version' ), ]), ] ); if ( is_wp_error( $resp ) ) { return $resp; } $code = (int) wp_remote_retrieve_response_code( $resp ); $body = (string) wp_remote_retrieve_body( $resp ); $data = json_decode( $body, true ); if ( 200 === $code && is_array( $data ) ) { return $data; } $msg = function_exists( 'bongoto_shop_license_api_error_message' ) ? bongoto_shop_license_api_error_message( $resp ) : ''; if ( '' === $msg ) { $msg = bongoto_shop_license_extract_error( $body ); } if ( '' === $msg ) { $msg = __( 'Purchase code validation failed.', 'bongoto-shop' ); } return new WP_Error( 'bongoto_purchase_validate_failed', $msg, [ 'http' => $code, 'body' => $body ] ); } /** * Optional server-side deactivation hook. * If your API doesn't support it, we still deactivate locally. */ function bongoto_shop_remote_deactivate_purchase_code( string $purchase_key ) { $purchase_key = sanitize_text_field( $purchase_key ); if ( '' === $purchase_key ) { return true; } $url = (string) BONGOTO_LICENSE_API_DEACTIVATE_URL; if ( '' === $url ) { return true; } $resp = wp_remote_post( $url, [ 'timeout' => 20, 'headers' => [ 'Content-Type' => 'application/json' ], 'body' => wp_json_encode([ 'product_id' => 'bongoto-shop', 'product_name' => 'Bongoto Shop', 'purchase_key' => $purchase_key, 'site_url' => rtrim( home_url(), '/' ), 'version' => wp_get_theme()->get( 'Version' ), ]), ] ); return is_wp_error( $resp ) ? $resp : true; } /** * Keep license status fresh in wp-admin. * - Re-check every 12 hours. * - Updates bongoto_premium_active based on server response. */ function bongoto_shop_maybe_refresh_license_status() : void { if ( ! is_admin() || ! current_user_can( 'manage_options' ) ) { return; } $purchase_key = (string) get_option( 'bongoto_purchase_code', '' ); if ( '' === trim( $purchase_key ) ) { return; } $last = (int) get_option( 'bongoto_premium_last_check', 0 ); $now = time(); if ( $last && ( $now - $last ) < ( 12 * HOUR_IN_SECONDS ) ) { return; } update_option( 'bongoto_premium_last_check', $now ); $resp = bongoto_shop_validate_purchase_code( $purchase_key ); if ( is_wp_error( $resp ) ) { // Do not force-disable on transient network errors. update_option( 'bongoto_premium_last_error', $resp->get_error_message() ); return; } update_option( 'bongoto_premium_last_error', '' ); $status = isset( $resp['status'] ) ? (string) $resp['status'] : ''; update_option( 'bongoto_premium_active', ( 'active' === $status ) ); if ( function_exists( 'bongoto_shop_store_license_meta' ) && is_array( $resp ) ) { bongoto_shop_store_license_meta( $resp ); } } function bongoto_shop_fetch_manifest() { $resp = wp_remote_get( BONGOTO_DEMO_MANIFEST_URL, [ 'timeout' => 20 ] ); if ( is_wp_error( $resp ) ) return $resp; $code = (int) wp_remote_retrieve_response_code( $resp ); $body = (string) wp_remote_retrieve_body( $resp ); $data = json_decode( $body, true ); if ( 200 === $code && is_array( $data ) ) return $data; return new WP_Error( 'bongoto_manifest_failed', 'Manifest fetch failed', [ 'http' => $code ] ); } /** * Download extra header templates to uploads folder: * wp-content/uploads/bongoto-shop/headers/ */ function bongoto_shop_download_extra_headers() { $manifest = bongoto_shop_fetch_manifest(); if ( is_wp_error( $manifest ) ) return $manifest; if ( empty( $manifest['headers'] ) || ! is_array( $manifest['headers'] ) ) { return new WP_Error( 'bongoto_headers_missing', 'No headers found in manifest.' ); } $upload = wp_upload_dir(); $base = trailingslashit( $upload['basedir'] ) . 'bongoto-shop/headers/'; wp_mkdir_p( $base ); $demo_base_url = trailingslashit( dirname( BONGOTO_DEMO_MANIFEST_URL ) ); foreach ( $manifest['headers'] as $h ) { if ( empty( $h['file'] ) ) { continue; } $rel = ltrim( (string) $h['file'], '/' ); if ( ! bongoto_shop_is_allowed_demo_file( $rel ) ) { continue; } $url = $demo_base_url . $rel; $r = wp_remote_get( $url, [ 'timeout' => 20 ] ); if ( is_wp_error( $r ) ) continue; $body = (string) wp_remote_retrieve_body( $r ); if ( '' === $body ) continue; $filename = basename( $rel ); bongoto_shop_fs_put_contents( $base . $filename, $body ); } // Also try to download shared header partials (CSS only). foreach ( array( 'headers/topbar.css' => 'topbar.css' ) as $remote => $local ) { $url = $demo_base_url . ltrim( $remote, '/' ); $resp = wp_remote_get( $url, [ 'timeout' => 25 ] ); if ( is_wp_error( $resp ) ) { continue; } $code = (int) wp_remote_retrieve_response_code( $resp ); $body = (string) wp_remote_retrieve_body( $resp ); if ( 200 !== $code || '' === $body ) { continue; } bongoto_shop_fs_put_contents( $base . $local, $body ); } return true; } /** * Returns local path of downloaded header, if exists. */ function bongoto_shop_get_downloaded_header_path( string $filename ) : string { $upload = wp_upload_dir(); $path = trailingslashit( $upload['basedir'] ) . 'bongoto-shop/headers/' . $filename; return file_exists( $path ) ? $path : ''; } /** * Download a file from demo base into uploads/bongoto-shop/templates// */ function bongoto_shop_download_demo_file_to_templates( string $relative_path, string $subdir = '' ) { $relative_path = ltrim( $relative_path, '/' ); $demo_base_url = trailingslashit( dirname( BONGOTO_DEMO_MANIFEST_URL ) ); $url = $demo_base_url . $relative_path; $resp = wp_remote_get( $url, [ 'timeout' => 25 ] ); if ( is_wp_error( $resp ) ) return $resp; $code = (int) wp_remote_retrieve_response_code( $resp ); $body = (string) wp_remote_retrieve_body( $resp ); if ( 200 !== $code || '' === $body ) { return new WP_Error( 'bongoto_demo_download_failed', 'Download failed', [ 'http' => $code, 'url' => $url ] ); } // Store demo assets inside the theme so Customizer reads from template-parts/demos/... // NOTE: This is required by the product spec (no uploads folder copies for presets/colors). $demo_slug = 'bongoto-shop'; $base = trailingslashit( get_theme_file_path( 'template-parts/demos/' . $demo_slug ) ); if ( $subdir ) { $base .= trim( $subdir, '/' ) . '/'; } wp_mkdir_p( $base ); $filename = basename( $relative_path ); $dest = $base . $filename; bongoto_shop_fs_put_contents( $dest, $body ); return $dest; } /** * Apply a Customizer preset JSON from manifest. * Expected JSON: * { * "theme_mods": { "bongoto_shop_color_bg":"#fff", ... } * } */ function bongoto_shop_apply_customizer_preset_from_manifest( string $preset_file ) { $dest = bongoto_shop_download_demo_file_to_templates( $preset_file, 'presets' ); if ( is_wp_error( $dest ) ) return $dest; $raw = file_get_contents( $dest ); $data = json_decode( (string) $raw, true ); if ( ! is_array( $data ) ) { return new WP_Error( 'bongoto_preset_invalid', 'Preset JSON invalid.' ); } $mods = $data['theme_mods'] ?? []; if ( is_array( $mods ) ) { foreach ( $mods as $k => $v ) { if ( ! is_string( $k ) || '' === $k ) { continue; } set_theme_mod( $k, $v ); } } return true; } /** * Download only selected headers listed in manifest into the theme demo folder. * (No uploads folder copies — required by the import flow spec.) */ function bongoto_shop_download_selected_headers( array $filenames, array $manifest ) { if ( empty( $manifest['headers'] ) || ! is_array( $manifest['headers'] ) ) { return new WP_Error( 'bongoto_headers_missing', 'No headers found in manifest.' ); } $need = array_map( 'basename', $filenames ); $need = array_values( array_filter( array_unique( $need ) ) ); $demo_slug = 'bongoto-shop'; $base_headers = trailingslashit( get_theme_file_path( 'template-parts/demos/' . $demo_slug . '/headers' ) ); wp_mkdir_p( $base_headers ); $demo_base_url = trailingslashit( dirname( BONGOTO_DEMO_MANIFEST_URL ) ); $downloaded = []; foreach ( $manifest['headers'] as $h ) { if ( empty( $h['file'] ) ) { continue; } $rel = ltrim( (string) $h['file'], '/' ); if ( ! bongoto_shop_is_allowed_demo_file( $rel ) ) { continue; } $filename = basename( (string) $h['file'] ); if ( ! in_array( $filename, $need, true ) ) continue; $resp = wp_remote_get( $demo_base_url . ltrim( (string) $h['file'], '/' ), [ 'timeout' => 25 ] ); if ( is_wp_error( $resp ) ) return $resp; $code = (int) wp_remote_retrieve_response_code( $resp ); $body = (string) wp_remote_retrieve_body( $resp ); if ( 200 !== $code || '' === $body ) { return new WP_Error( 'bongoto_header_download_failed', 'Header download failed', [ 'file' => $filename, 'http' => $code ] ); } // Save only inside theme demo folder. bongoto_shop_fs_put_contents( $base_headers . $filename, $body ); $downloaded[] = $filename; } update_option( 'bongoto_downloaded_headers', $downloaded ); return true; }