<?php /** * Load full RTP slot dataset. * * @package RTP_Slot_Space */ if ( ! defined( 'ABSPATH' ) ) { exit; } /** * Path to bundled full RTP JSON. * * @return string */ function rtp_slot_space_rtp_json_path() { return RTP_SLOT_SPACE_DIR . '/data/rtp-slots-full.json'; } /** * Load RTP sections from JSON file. * * @return array */ function rtp_slot_space_load_rtp_from_file() { $path = rtp_slot_space_rtp_json_path(); if ( ! file_exists( $path ) ) { return array(); } $raw = file_get_contents( $path ); if ( false === $raw || '' === trim( $raw ) ) { return array(); } $data = json_decode( $raw, true ); if ( JSON_ERROR_NONE !== json_last_error() || ! is_array( $data ) ) { return array(); } return $data; } /** * Minimal fallback if JSON file missing. * * @return array */ function rtp_slot_space_rtp_fallback_data() { return array( array( 'provider_label' => 'PRAGMATIC PLAY', 'slots' => array( array( 'name' => 'Gates of Olympus', 'rtp' => 96.5, 'status' => 'gacor', 'volatility' => 'Tinggi', 'max_win' => '5000x', 'pola' => array( array( 'step' => 1, 'bet' => '800', 'spin' => '10', 'mode' => 'Manual', 'note' => 'Spin manual', ), ), 'cara_bermain' => 'Pilih bet minimal, aktifkan turbo spin, targetkan scatter.', ), ), ), ); } /** * Full default RTP data (bundled JSON). * * @return array */ function rtp_slot_space_default_rtp_data() { $file_data = rtp_slot_space_load_rtp_from_file(); if ( ! empty( $file_data ) ) { return $file_data; } return rtp_slot_space_rtp_fallback_data(); } /** * Export full RTP JSON string for admin/seed. * * @return string */ function rtp_slot_space_get_full_rtp_json() { return wp_json_encode( rtp_slot_space_default_rtp_data(), JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE ); } /** * Count total slots in dataset. * * @param array $sections Provider sections. * @return int */ function rtp_slot_space_count_rtp_slots( $sections ) { $total = 0; foreach ( $sections as $section ) { if ( ! empty( $section['slots'] ) && is_array( $section['slots'] ) ) { $total += count( $section['slots'] ); } } return $total; } /** * Get RTP sections from admin option or default. * * @return array */ function rtp_slot_space_get_rtp_sections() { $json = get_option( 'rtp_slot_rtp_json', '' ); if ( $json ) { $decoded = json_decode( $json, true ); if ( is_array( $decoded ) && ! empty( $decoded ) ) { return rtp_slot_space_apply_auto_rtp( $decoded ); } } $data = rtp_slot_space_load_rtp_from_file(); if ( empty( $data ) ) { $data = rtp_slot_space_rtp_fallback_data(); } return rtp_slot_space_apply_auto_rtp( $data ); } /** * Seed RTP data into options. * * @param bool $force Overwrite existing. */ function rtp_slot_space_seed_rtp_data( $force = false ) { if ( ! $force && get_option( 'rtp_slot_rtp_json', '' ) ) { return; } update_option( 'rtp_slot_rtp_json', rtp_slot_space_get_full_rtp_json() ); } <?php /** * Automatic RTP update engine (cron + transient cache). * * @package RTP_Slot_Space */ if ( ! defined( 'ABSPATH' ) ) { exit; } define( 'RTP_SLOT_AUTO_CRON_HOOK', 'rtp_slot_space_auto_update' ); /** * Schedule cron on theme activation. */ function rtp_slot_space_schedule_auto_update() { if ( ! wp_next_scheduled( RTP_SLOT_AUTO_CRON_HOOK ) ) { wp_schedule_event( time(), 'rtp_slot_15min', RTP_SLOT_AUTO_CRON_HOOK ); } } add_action( 'after_switch_theme', 'rtp_slot_space_schedule_auto_update' ); /** * Custom cron interval - every 15 minutes. * * @param array $schedules Schedules. * @return array */ function rtp_slot_space_cron_schedules( $schedules ) { $schedules['rtp_slot_15min'] = array( 'interval' => 900, 'display' => 'Setiap 15 Menit (RTP Slot)', ); return $schedules; } add_filter( 'cron_schedules', 'rtp_slot_space_cron_schedules' ); /** * Apply auto RTP variation to sections. * * @param array $sections Provider sections. * @return array */ function rtp_slot_space_apply_auto_rtp( $sections ) { if ( ! get_theme_mod( 'rtp_slot_auto_rtp_enabled', true ) ) { return $sections; } $cache_key = 'rtp_slot_auto_' . md5( wp_json_encode( $sections ) . wp_date( 'Y-m-d-H' ) ); $cached = get_transient( $cache_key ); if ( false !== $cached && is_array( $cached ) ) { return $cached; } $seed = (int) wp_date( 'YmdH' ) + (int) ( time() / 900 ); $result = array(); foreach ( $sections as $pi => $provider ) { $slots = isset( $provider['slots'] ) && is_array( $provider['slots'] ) ? $provider['slots'] : array(); $new_slots = array(); foreach ( $slots as $si => $slot ) { $base_rtp = isset( $slot['rtp'] ) ? (float) $slot['rtp'] : 95.0; $hash = crc32( (string) ( $pi * 100 + $si ) . $seed ); $delta = ( ( $hash % 21 ) - 10 ) / 10; $new_rtp = round( min( 99.0, max( 88.0, $base_rtp + $delta ) ), 1 ); $slot['rtp'] = $new_rtp; $slot['rtp_previous'] = $base_rtp; $slot['status'] = rtp_slot_space_rtp_status( $new_rtp ); $slot['updated'] = wp_date( 'H:i' ) . ' WIB'; $new_slots[] = $slot; } $provider['slots'] = $new_slots; $result[] = $provider; } set_transient( $cache_key, $result, 900 ); return $result; } /** * Map RTP value to status label. * * @param float $rtp RTP percentage. * @return string */ function rtp_slot_space_rtp_status( $rtp ) { $rtp = (float) $rtp; if ( $rtp >= 96.5 ) { return 'gacor'; } if ( $rtp >= 95.0 ) { return 'panas'; } if ( $rtp >= 93.0 ) { return 'normal'; } return 'dingin'; } /** * Human-readable status label. * * @param string $status Status key. * @return string */ function rtp_slot_space_status_label( $status ) { $labels = array( 'gacor' => 'GACOR', 'panas' => 'PANAS', 'normal' => 'NORMAL', 'dingin' => 'DINGIN', ); $key = strtolower( trim( (string) $status ) ); return isset( $labels[ $key ] ) ? $labels[ $key ] : strtoupper( $key ); } /** * CSS class for status badge. * * @param string $status Status key. * @return string */ function rtp_slot_space_status_class( $status ) { return 'rtp-status-' . sanitize_html_class( strtolower( trim( (string) $status ) ) ); } /** * Cron handler - refresh transients. */ function rtp_slot_space_cron_auto_update() { delete_transient( 'rtp_slot_ticker_data' ); rtp_slot_space_get_rtp_sections(); rtp_slot_space_get_ticker_items(); } add_action( RTP_SLOT_AUTO_CRON_HOOK, 'rtp_slot_space_cron_auto_update' ); /** * Build jackpot/win ticker items from RTP data. * * @return array */ function rtp_slot_space_get_ticker_items() { $cached = get_transient( 'rtp_slot_ticker_data' ); if ( false !== $cached && is_array( $cached ) ) { return $cached; } $sections = rtp_slot_space_get_rtp_sections(); $items = array(); $names = array( 'Budi***', 'Siti***', 'Agus***', 'Rina***', 'Dedi***', 'Maya***', 'Rizky***', 'Lina***' ); $amounts = array( 'Rp 2.450.000', 'Rp 5.800.000', 'Rp 1.200.000', 'Rp 8.900.000', 'Rp 3.600.000', 'Rp 12.500.000', 'Rp 780.000', 'Rp 4.200.000' ); $slot_names = array(); foreach ( $sections as $provider ) { if ( empty( $provider['slots'] ) ) { continue; } foreach ( $provider['slots'] as $slot ) { if ( ! empty( $slot['name'] ) ) { $slot_names[] = $slot['name']; } } } if ( empty( $slot_names ) ) { $slot_names = array( 'Gates of Olympus', 'Starlight Princess', 'Mahjong Ways' ); } for ( $i = 0; $i < 12; $i++ ) { $items[] = array( 'player' => $names[ $i % count( $names ) ], 'slot' => $slot_names[ $i % count( $slot_names ) ], 'amount' => $amounts[ $i % count( $amounts ) ], 'time' => sprintf( "%d'", ( $i * 7 + 3 ) % 59 + 1 ), ); } set_transient( 'rtp_slot_ticker_data', $items, 900 ); return $items; } /** * RTP title with Indonesian date. * * @return string */ function rtp_slot_space_rtp_title() { $months = array( 1 => 'Januari', 2 => 'Februari', 3 => 'Maret', 4 => 'April', 5 => 'Mei', 6 => 'Juni', 7 => 'Juli', 8 => 'Agustus', 9 => 'September', 10 => 'Oktober', 11 => 'November', 12 => 'Desember', ); $day = (int) wp_date( 'j' ); $month = (int) wp_date( 'n' ); $year = wp_date( 'Y' ); return sprintf( 'RTP Slot Live Hari Ini %d %s %s', $day, isset( $months[ $month ] ) ? $months[ $month ] : wp_date( 'F' ), $year ); } <?php /** * Canonical URL and SEO meta - domain joker88.gratis * * @package RTP_Slot_Space */ if ( ! defined( 'ABSPATH' ) ) { exit; } /** * Canonical base URL (no trailing slash). * * @return string */ function rtp_slot_space_get_canonical_base() { $domain = get_theme_mod( 'rtp_slot_canonical_domain', 'https://joker88.gratis' ); $domain = esc_url_raw( trim( (string) $domain ) ); if ( '' === $domain ) { $domain = 'https://joker88.gratis'; } return untrailingslashit( $domain ); } /** * Relative path for current page. * * @return string */ function rtp_slot_space_get_canonical_path() { if ( is_singular() ) { $post = get_queried_object(); if ( $post instanceof WP_Post ) { return ltrim( wp_make_link_relative( get_permalink( $post ) ), '/' ); } } if ( is_category() || is_tag() || is_tax() ) { $term = get_queried_object(); if ( $term ) { $link = get_term_link( $term ); if ( ! is_wp_error( $link ) ) { return ltrim( wp_make_link_relative( $link ), '/' ); } } } if ( is_post_type_archive() ) { $link = get_post_type_archive_link( get_post_type() ); if ( $link ) { return ltrim( wp_make_link_relative( $link ), '/' ); } } global $wp; return isset( $wp->request ) ? trim( (string) $wp->request, '/' ) : ''; } /** * Build canonical URL for active page. * * @return string */ function rtp_slot_space_get_canonical_url() { if ( is_search() || is_404() ) { return ''; } $base = rtp_slot_space_get_canonical_base(); if ( ( is_front_page() || is_home() ) && ! is_paged() ) { return trailingslashit( $base ); } $path = rtp_slot_space_get_canonical_path(); $url = $path ? trailingslashit( $base ) . $path : trailingslashit( $base ); $paged = (int) get_query_var( 'paged' ); if ( $paged < 1 ) { $paged = (int) get_query_var( 'page' ); } if ( $paged > 1 ) { $url = trailingslashit( $url ) . 'page/' . $paged . '/'; } return user_trailingslashit( $url ); } /** * Homepage SEO meta defaults. * * @return array */ function rtp_slot_space_homepage_seo_meta() { $brand = get_bloginfo( 'name' ); $base = rtp_slot_space_get_canonical_base(); return array( 'title' => $brand . ' - RTP Slot Live Otomatis, Pola Gacor & Cara Bermain Terpercaya', 'description' => $brand . ' menyajikan RTP slot live otomatis diperbarui setiap 15 menit. Pola gacor, cara bermain, dan info slot gacor Pragmatic Play, PG Soft, Habanero - ' . $base, 'focus_kw' => 'rtp slot, rtp slot live, pola gacor, slot gacor', ); } /** * Document title for homepage. * * @param array $parts Title parts. * @return array */ function rtp_slot_space_document_title_parts( $parts ) { if ( ! is_front_page() && ! is_home() ) { return $parts; } if ( defined( 'RANK_MATH_VERSION' ) || defined( 'WPSEO_VERSION' ) ) { return $parts; } $meta = rtp_slot_space_homepage_seo_meta(); $parts['title'] = $meta['title']; return $parts; } add_filter( 'document_title_parts', 'rtp_slot_space_document_title_parts', 20 ); /** * Output canonical, Open Graph, and JSON-LD in head. */ function rtp_slot_space_output_canonical_head() { $canonical = rtp_slot_space_get_canonical_url(); $base = rtp_slot_space_get_canonical_base(); $site_name = get_bloginfo( 'name' ); if ( is_search() || is_404() ) { echo '<meta name="robots" content="noindex, follow">' . "\n"; return; } if ( $canonical ) { echo '<link rel="canonical" href="' . esc_url( $canonical ) . '">' . "\n"; } echo '<meta property="og:type" content="' . ( is_singular( 'post' ) ? 'article' : 'website' ) . '">' . "\n"; echo '<meta property="og:site_name" content="' . esc_attr( $site_name ) . '">' . "\n"; echo '<meta property="og:locale" content="id_ID">' . "\n"; if ( $canonical ) { echo '<meta property="og:url" content="' . esc_url( $canonical ) . '">' . "\n"; } if ( is_front_page() || is_home() ) { $meta = rtp_slot_space_homepage_seo_meta(); echo '<meta name="description" content="' . esc_attr( $meta['description'] ) . '">' . "\n"; echo '<meta name="keywords" content="' . esc_attr( $meta['focus_kw'] ) . '">' . "\n"; echo '<meta property="og:title" content="' . esc_attr( $meta['title'] ) . '">' . "\n"; echo '<meta property="og:description" content="' . esc_attr( $meta['description'] ) . '">' . "\n"; } elseif ( is_singular() ) { $title = get_the_title(); $desc = has_excerpt() ? get_the_excerpt() : wp_trim_words( wp_strip_all_tags( get_the_content() ), 30, '...' ); echo '<meta name="description" content="' . esc_attr( $desc ) . '">' . "\n"; echo '<meta property="og:title" content="' . esc_attr( $title ) . '">' . "\n"; echo '<meta property="og:description" content="' . esc_attr( $desc ) . '">' . "\n"; } $logo = rtp_slot_space_get_logo_url(); if ( $logo ) { echo '<meta property="og:image" content="' . esc_url( $logo ) . '">' . "\n"; } $schema = array( '@context' => 'https://schema.org', '@type' => 'WebSite', 'name' => $site_name, 'url' => trailingslashit( $base ), 'description' => rtp_slot_space_homepage_seo_meta()['description'], 'inLanguage' => 'id-ID', ); echo '<script type="application/ld+json">' . wp_json_encode( $schema, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES ) . '</script>' . "\n"; } add_action( 'wp_head', 'rtp_slot_space_output_canonical_head', 1 ); /** * Prevent redirect_canonical conflict when WP domain differs. * * @param string|false $redirect_url Redirect URL. * @return string|false */ function rtp_slot_space_filter_redirect_canonical( $redirect_url ) { if ( is_search() || is_404() ) { return false; } $canonical = rtp_slot_space_get_canonical_url(); if ( ! $canonical ) { return $redirect_url; } $current = ( is_ssl() ? 'https://' : 'http://' ) . ( isset( $_SERVER['HTTP_HOST'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_HOST'] ) ) : '' ); $current .= isset( $_SERVER['REQUEST_URI'] ) ? esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : ''; if ( untrailingslashit( $current ) === untrailingslashit( $canonical ) ) { return false; } return $redirect_url; } add_filter( 'redirect_canonical', 'rtp_slot_space_filter_redirect_canonical', 10, 1 );