put_contents( $path, $contents, FS_CHMOD_FILE ); } return false !== @file_put_contents( $path, $contents ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_file_put_contents } /** * Build safe args for remote requests to the demo/license server. */ function bongoto_shop_remote_get_args( int $timeout = 20, int $max_bytes = 10485760 ) : array { return array( 'timeout' => $timeout, 'redirection' => 3, 'reject_unsafe_urls' => true, 'sslverify' => true, 'limit_response_size'=> $max_bytes, 'user-agent' => 'BongotoShopTheme/' . ( defined( 'BONGOTO_SHOP_VERSION' ) ? BONGOTO_SHOP_VERSION : '1.0.0' ) . '; ' . home_url(), ); } /** * Ensure demo asset URLs only come from the same host as the manifest URL. */ function bongoto_shop_is_allowed_demo_host( string $url ) : bool { $m = wp_parse_url( BONGOTO_DEMO_MANIFEST_URL ); $u = wp_parse_url( $url ); if ( empty( $m['host'] ) || empty( $u['host'] ) ) return false; $mh = strtolower( (string) $m['host'] ); $uh = strtolower( (string) $u['host'] ); if ( $mh !== $uh ) return false; // Enforce https when manifest is https. if ( ! empty( $m['scheme'] ) && 'https' === strtolower( (string) $m['scheme'] ) ) { if ( empty( $u['scheme'] ) || 'https' !== strtolower( (string) $u['scheme'] ) ) { return false; } } return true; } /** * Extract error message from license server responses. */ function bongoto_shop_license_api_error_message( $response ) : string { $body = wp_remote_retrieve_body( $response ); if ( ! $body ) { return ''; } $json = json_decode( $body, true ); if ( is_array( $json ) && ! empty( $json['error'] ) ) { return sanitize_text_field( (string) $json['error'] ); } return ''; } // Demo manifest URL (IMPORTANT: your Hostinger subdomain directory is /public_html/license) if ( ! defined( 'BONGOTO_DEMO_MANIFEST_URL' ) ) { define( 'BONGOTO_DEMO_MANIFEST_URL', 'https://license.bongoto.com/demos/bongoto-shop/manifest.json' ); } function bongoto_shop_get_instance_id() : string { $instance = (string) get_option( 'bongoto_license_instance', '' ); if ( $instance ) return $instance; // Stable-ish per-site identifier (32 hex chars) $seed = home_url() . '|' . ( function_exists( 'wp_salt' ) ? wp_salt() : (string) AUTH_KEY ); $instance = md5( $seed ); update_option( 'bongoto_license_instance', $instance ); return $instance; } /** * Extract a readable error message from license server JSON response. */ function bongoto_shop_license_extract_error( string $body ) : string { $data = json_decode( $body, true ); if ( is_array( $data ) && isset( $data['error'] ) && is_string( $data['error'] ) ) { $msg = trim( $data['error'] ); return $msg; } return ''; } /** * Store license meta returned by the license server. * Used to render a richer UI on the Purchase Code page. */ function bongoto_shop_store_license_meta( array $resp ) : void { $domain_limit = null; if ( isset( $resp['domain_limit'] ) ) { $domain_limit = (int) $resp['domain_limit']; } elseif ( isset( $resp['seats'] ) ) { $domain_limit = (int) $resp['seats']; } $domains_used = null; if ( isset( $resp['domains_used'] ) ) { $domains_used = (int) $resp['domains_used']; } $source = ''; if ( isset( $resp['source'] ) ) { $source = sanitize_text_field( (string) $resp['source'] ); } elseif ( ! empty( $resp['email'] ) && is_string( $resp['email'] ) && false !== strpos( $resp['email'], '@envato.local' ) ) { $source = 'envato'; } else { $source = 'direct'; } $meta = [ 'status' => 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; } $now = time(); $last = (int) get_option( 'bongoto_premium_last_check', 0 ); // If the site URL changed (common after migration/cloning), force a fresh validation // even if the 12h throttle window hasn't elapsed. $current_site = rtrim( home_url(), '/' ); $last_site = (string) get_option( 'bongoto_premium_last_siteurl', '' ); $site_changed = ( $last_site && $last_site !== $current_site ); if ( ! $site_changed && $last && ( $now - $last ) < ( 12 * HOUR_IN_SECONDS ) ) { return; } update_option( 'bongoto_premium_last_check', $now ); update_option( 'bongoto_premium_last_siteurl', $current_site ); $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, bongoto_shop_remote_get_args( 20, 1024 * 1024 ) ); 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; if ( ! bongoto_shop_is_allowed_demo_host( $url ) ) { return new WP_Error( 'bongoto_invalid_demo_host', 'Invalid demo source URL', array( 'url' => $url ) ); } $r = wp_remote_get( $url, bongoto_shop_remote_get_args( 20, 10 * 1024 * 1024 ) ); 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, '/' ); if ( ! bongoto_shop_is_allowed_demo_host( $url ) ) { return new WP_Error( 'bongoto_invalid_demo_host', 'Invalid demo source URL', array( 'url' => $url ) ); } $resp = wp_remote_get( $url, bongoto_shop_remote_get_args( 25, 10 * 1024 * 1024 ) ); 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; if ( ! bongoto_shop_is_allowed_demo_host( $url ) ) { return new WP_Error( 'bongoto_invalid_demo_host', 'Invalid demo source URL', array( 'url' => $url ) ); } $resp = wp_remote_get( $url, bongoto_shop_remote_get_args( 25, 10 * 1024 * 1024 ) ); 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; }