<?php /** * Plugin Name: RN Native Data Layer (CPT Project + Donasi) * Plugin URI: https://rumpunnurani.org * Description: Lapisan native pengganti CPT `project` + CCT `donasi` (JetEngine). Menyediakan post type native, meta terdaftar, shortcode rendering, dan form donasi mandiri. Aman dipertahankan setelah JetEngine dinonaktifkan. * Version: 1.2.0 * Author: Tim Media RN * License: GPL-2.0+ * Requires PHP: 7.4 */ if ( ! defined( 'ABSPATH' ) ) { exit; } define( 'RN_ND_VER', '1.2.0' ); /* ============================================================ * 1) CPT PROJECT * ============================================================ */ add_action( 'init', 'rn_register_project_cpt' ); function rn_register_project_cpt() { register_post_type( 'project', array( 'labels' => array( 'name' => 'Project', 'singular_name' => 'Project', 'menu_name' => 'Project', 'add_new' => 'Tambah Project', 'add_new_item' => 'Tambah Project Baru', 'edit_item' => 'Edit Project', 'new_item' => 'Project Baru', 'view_item' => 'Lihat Project', 'search_items' => 'Cari Project', 'not_found' => 'Tidak ada Project', 'not_found_in_trash' => 'Tidak ada Project di Sampah', ), 'public' => true, 'publicly_queryable' => true, 'show_ui' => true, 'show_in_menu' => true, 'show_in_nav_menus' => true, 'show_in_rest' => true, 'query_var' => true, 'has_archive' => true, 'hierarchical' => false, 'exclude_from_search' => false, 'menu_position' => 25, 'menu_icon' => 'dashicons-heart', 'capability_type' => 'post', 'supports' => array( 'title', 'editor', 'thumbnail', 'excerpt', 'comments' ), 'rewrite' => array( 'slug' => 'project', 'with_front' => false ), ) ); } add_action( 'init', 'rn_register_project_meta' ); function rn_register_project_meta() { $keys = array( 'target-jumlah-donasi', 'deskripsi-singkat', 'deskripsi-lengkap' ); foreach ( $keys as $k ) { register_post_meta( 'project', $k, array( 'show_in_rest' => true, 'single' => true, 'type' => 'string', 'sanitize_callback' => ( $k === 'deskripsi-lengkap' ) ? 'wp_kses_post' : 'sanitize_textarea_field', 'auth_callback' => 'rn_meta_auth', ) ); } } function rn_meta_auth( $allowed, $meta_key, $object_id, $user_id, $cap, $caps ) { return current_user_can( 'edit_post', $object_id ); } /* admin column: Target Donasi */ add_filter( 'manage_project_posts_columns', 'rn_project_columns' ); function rn_project_columns( $cols ) { $cols['rn_target'] = 'Target Donasi'; $cols['rn_collected'] = 'Terkumpul'; return $cols; } add_action( 'manage_project_posts_custom_column', 'rn_project_column_value', 10, 2 ); function rn_project_column_value( $col, $post_id ) { if ( $col === 'rn_target' ) { $v = get_post_meta( $post_id, 'target-jumlah-donasi', true ); echo $v !== '' ? 'Rp ' . number_format_i18n( (float) $v ) : '—'; } elseif ( $col === 'rn_collected' ) { $coll = rn_get_project_collected_amount( $post_id ); echo 'Rp ' . number_format_i18n( $coll ); } } /* ============================================================ * 2) CPT DONASI (target migrasi CCT `donasi`) * ============================================================ */ add_action( 'init', 'rn_register_donasi_cpt' ); function rn_register_donasi_cpt() { register_post_type( 'donasi', array( 'labels' => array( 'name' => 'Donasi', 'singular_name' => 'Donasi', 'menu_name' => 'Donasi Masuk', 'all_items' => 'Semua Donasi', 'edit_item' => 'Edit Donasi', 'search_items' => 'Cari Donasi', 'not_found' => 'Belum ada donasi', ), 'public' => false, 'publicly_queryable' => false, 'show_ui' => true, 'show_in_menu' => 'edit.php?post_type=project', 'show_in_nav_menus' => false, 'show_in_rest' => true, 'exclude_from_search' => true, 'has_archive' => false, 'hierarchical' => false, 'supports' => array( 'title' ), 'rewrite' => false, 'capability_type' => 'post', ) ); $dkeys = array( 'jumlah_donasi', 'project_pilihan', 'tampilkan_nama_donatur', 'nama_donatur', 'nomor_wa' ); foreach ( $dkeys as $k ) { register_post_meta( 'donasi', $k, array( 'show_in_rest' => true, 'single' => true, 'type' => 'string', 'auth_callback' => 'rn_meta_auth', ) ); } } add_filter( 'manage_donasi_posts_columns', function( $cols ) { return array( 'cb' => isset( $cols['cb'] ) ? $cols['cb'] : '', 'title' => 'Donatur', 'rn_project' => 'Project', 'rn_amount' => 'Jumlah Donasi', 'rn_show' => 'Tampilkan?', 'rn_wa' => 'WhatsApp', 'date' => 'Tanggal', ); } ); add_action( 'manage_donasi_posts_custom_column', function( $col, $post_id ) { if ( $col === 'rn_amount' ) { $v = get_post_meta( $post_id, 'jumlah_donasi', true ); echo $v !== '' ? '<strong>Rp ' . number_format_i18n( (float) $v ) . '</strong>' : '—'; } elseif ( $col === 'rn_project' ) { $pid = get_post_meta( $post_id, 'project_pilihan', true ); echo $pid ? '<a href="' . esc_url( get_edit_post_link( (int) $pid ) ) . '">' . esc_html( get_the_title( (int) $pid ) ) . '</a>' : '—'; } elseif ( $col === 'rn_show' ) { $tamp = get_post_meta( $post_id, 'tampilkan_nama_donatur', true ); echo ( $tamp === 'ya' || $tamp === 'yes' || $tamp === '1' ) ? '<span style="color:#10b981;font-weight:600;">Ya</span>' : '<span style="color:#94a3b8;">Anonim</span>'; } elseif ( $col === 'rn_wa' ) { echo esc_html( get_post_meta( $post_id, 'nomor_wa', true ) ); } }, 10, 2 ); /* ============================================================ * 3) CORE DATA HELPERS * ============================================================ */ function rn_current_post_id( $atts = array() ) { if ( ! empty( $atts['id'] ) ) { return (int) $atts['id']; } $id = get_the_ID(); if ( $id ) { return (int) $id; } global $post; return $post ? (int) $post->ID : 0; } function rn_rupiah( $v, $prefix = 'Rp.' ) { if ( $v === '' || $v === null ) { return ''; } return $prefix . number_format( (float) $v, 0, ',', '.' ); } function rn_get_project_collected_amount( $project_id ) { global $wpdb; $project_id = (int) $project_id; if ( ! $project_id ) { return 0; } /* 1. Sum from native 'donasi' posts */ $sql_native = $wpdb->prepare( "SELECT SUM(CAST(pm.meta_value AS DECIMAL(15,2))) FROM {$wpdb->posts} p INNER JOIN {$wpdb->postmeta} pm ON (p.ID = pm.post_id AND pm.meta_key = 'jumlah_donasi') INNER JOIN {$wpdb->postmeta} pm_proj ON (p.ID = pm_proj.post_id AND pm_proj.meta_key = 'project_pilihan') WHERE p.post_type = 'donasi' AND p.post_status = 'publish' AND pm_proj.meta_value = %s", (string) $project_id ); $sum_native = (float) $wpdb->get_var( $sql_native ); if ( $sum_native > 0 ) { return $sum_native; } /* 2. Fallback check CCT table if native donasi has not been populated yet */ $tbl = $wpdb->prefix . 'jet_cct_donasi'; if ( $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $tbl ) ) === $tbl ) { $sql_cct = $wpdb->prepare( "SELECT SUM(CAST(jumlah_donasi AS DECIMAL(15,2))) FROM {$tbl} WHERE cct_status = 'publish' AND project_pilihan = %s", (string) $project_id ); $sum_cct = (float) $wpdb->get_var( $sql_cct ); if ( $sum_cct > 0 ) { return $sum_cct; } } return 0; } function rn_format_date_id( $date_str ) { if ( ! $date_str ) { return '—'; } $ts = strtotime( $date_str ); if ( ! $ts ) { return $date_str; } $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' ); $d = date( 'j', $ts ); $m = $months[ (int) date( 'n', $ts ) ]; $y = date( 'Y', $ts ); return "{$d} {$m} {$y}"; } /* ============================================================ * 4) SHORTCODE: [rn_project_field] * ============================================================ */ add_shortcode( 'rn_project_field', 'rn_sc_project_field' ); function rn_sc_project_field( $atts ) { $a = shortcode_atts( array( 'key' => 'target-jumlah-donasi', 'id' => 0, 'format' => '', 'prefix' => '', 'suffix' => '', 'raw' => '', ), $atts, 'rn_project_field' ); $pid = rn_current_post_id( $a ); if ( ! $pid ) { return ''; } $val = ''; if ( $a['key'] === 'terkumpul' || $a['key'] === 'collected' ) { $amount = rn_get_project_collected_amount( $pid ); $val = ( $a['format'] === 'currency' || $a['format'] === 'rupiah' ) ? rn_rupiah( $amount ) : $amount; } elseif ( $a['key'] === 'progress' || $a['key'] === 'persen' ) { $target = (float) get_post_meta( $pid, 'target-jumlah-donasi', true ); $coll = rn_get_project_collected_amount( $pid ); $pct = $target > 0 ? min( 100, round( ( $coll / $target ) * 100, 1 ) ) : 0; $val = ( $a['format'] === 'number' ) ? $pct : $pct . '%'; } else { $val = get_post_meta( $pid, $a['key'], true ); if ( $val === '' || $val === null ) { return ''; } if ( $a['raw'] !== 'yes' ) { if ( $a['format'] === 'currency' || $a['format'] === 'rupiah' ) { $val = rn_rupiah( $val ); } elseif ( $a['key'] === 'deskripsi-lengkap' ) { $val = wp_kses_post( wpautop( $val ) ); } } } return $a['prefix'] . $val . $a['suffix']; } /* ============================================================ * 5) SHORTCODE: [rn_project_grid] * ============================================================ */ add_shortcode( 'rn_project_grid', 'rn_sc_project_grid' ); function rn_sc_project_grid( $atts ) { $a = shortcode_atts( array( 'columns' => 3, 'count' => 12, 'orderby' => 'date', 'order' => 'DESC', 'class' => '', 'exclude' => '', ), $atts, 'rn_project_grid' ); $query_args = array( 'post_type' => 'project', 'posts_per_page' => (int) $a['count'], 'orderby' => $a['orderby'], 'order' => $a['order'], 'post_status' => 'publish', ); if ( ! empty( $a['exclude'] ) ) { $query_args['post__not_in'] = array_map( 'intval', explode( ',', $a['exclude'] ) ); } $q = new WP_Query( $query_args ); if ( ! $q->have_posts() ) { return '<p style="text-align:center;color:#64748b;padding:30px 0;">Belum ada project donasi yang aktif.</p>'; } $cols = max( 1, (int) $a['columns'] ); $uid = 'rnpg-' . wp_rand( 1000, 9999 ); $css = " #{$uid}{display:grid;grid-template-columns:repeat({$cols},minmax(0,1fr));gap:24px;} #{$uid} .rnpg-card{display:flex;flex-direction:column;background:#fff;border:1px solid #f1f5f9;border-radius:18px;overflow:hidden;text-decoration:none;transition:transform .25s ease,box-shadow .25s ease;} #{$uid} .rnpg-card:hover{transform:translateY(-6px);box-shadow:0 16px 36px rgba(15,23,42,.12);border-color:#e2e8f0;} #{$uid} .rnpg-thumb{aspect-ratio:16/10;overflow:hidden;background:#f8fafc;position:relative;} #{$uid} .rnpg-thumb img{width:100%;height:100%;object-fit:cover;display:block;margin:0;transition:transform .35s ease;} #{$uid} .rnpg-card:hover .rnpg-thumb img{transform:scale(1.03);} #{$uid} .rnpg-body{padding:20px 22px 24px;display:flex;flex-direction:column;gap:12px;flex:1;} #{$uid} .rnpg-title{font-family:'Poppins',sans-serif;font-size:17px;line-height:1.45;font-weight:600;color:#18181b;margin:0;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;} #{$uid} .rnpg-prog-wrap{margin-top:auto;display:flex;flex-direction:column;gap:8px;} #{$uid} .rnpg-prog-bar-bg{width:100%;height:7px;background:#f1f5f9;border-radius:999px;overflow:hidden;} #{$uid} .rnpg-prog-bar-fill{height:100%;background:linear-gradient(90deg,#802829 0%,#dc2626 100%);border-radius:999px;transition:width .5s ease;} #{$uid} .rnpg-meta-grid{display:flex;justify-content:space-between;align-items:flex-end;font-size:12px;color:#71717a;border-top:1px solid #f8fafc;padding-top:10px;} #{$uid} .rnpg-meta-col{display:flex;flex-direction:column;gap:2px;} #{$uid} .rnpg-meta-label{font-size:11px;text-transform:uppercase;letter-spacing:.04em;color:#a1a1aa;font-weight:500;} #{$uid} .rnpg-meta-val{font-size:13px;font-weight:700;color:#18181b;} #{$uid} .rnpg-meta-val.highlight{color:#802829;} #{$uid} .rnpg-btn{margin-top:6px;width:100%;text-align:center;padding:10px 16px;background:linear-gradient(90deg,#802829 0%,#dc2626 100%);color:#fff;font-family:'Poppins',sans-serif;font-size:13px;font-weight:600;border-radius:10px;text-decoration:none;box-sizing:border-box;display:inline-block;transition:opacity .2s ease;} #{$uid} .rnpg-btn:hover{opacity:.92;color:#fff;} @media(max-width:1024px){#{$uid}{grid-template-columns:repeat(min(2,{$cols}),minmax(0,1fr));gap:20px;}} @media(max-width:640px){#{$uid}{grid-template-columns:1fr;gap:18px;}} "; $html = '<style>' . $css . '</style>'; $html .= '<div id="' . esc_attr( $uid ) . '" class="rn-project-grid ' . esc_attr( $a['class'] ) . '">'; while ( $q->have_posts() ) { $q->the_post(); $pid = get_the_ID(); $link = get_permalink( $pid ); $thumb = get_the_post_thumbnail( $pid, 'large', array( 'loading' => 'lazy', 'alt' => esc_attr( get_the_title() ) ) ); $target = (float) get_post_meta( $pid, 'target-jumlah-donasi', true ); if ( ! $target ) { $target = 1000000000; } $coll = rn_get_project_collected_amount( $pid ); $pct = min( 100, round( ( $coll / $target ) * 100, 1 ) ); $html .= '<div class="rnpg-card">'; $html .= '<a class="rnpg-thumb" href="' . esc_url( $link ) . '">' . ( $thumb ? $thumb : '<div style="width:100%;height:100%;background:#f1f5f9;"></div>' ) . '</a>'; $html .= '<div class="rnpg-body">'; $html .= '<a href="' . esc_url( $link ) . '" style="text-decoration:none;"><h3 class="rnpg-title">' . esc_html( get_the_title() ) . '</h3></a>'; $html .= '<div class="rnpg-prog-wrap">'; $html .= '<div class="rnpg-prog-bar-bg"><div class="rnpg-prog-bar-fill" style="width:' . esc_attr( $pct ) . '%;"></div></div>'; $html .= '<div class="rnpg-meta-grid">'; $html .= '<div class="rnpg-meta-col"><span class="rnpg-meta-label">Terkumpul</span><span class="rnpg-meta-val highlight">' . esc_html( rn_rupiah( $coll ) ) . '</span></div>'; $html .= '<div class="rnpg-meta-col" style="text-align:right;"><span class="rnpg-meta-label">Target</span><span class="rnpg-meta-val">' . esc_html( rn_rupiah( $target ) ) . '</span></div>'; $html .= '</div>'; $html .= '<a class="rnpg-btn" href="' . esc_url( $link ) . '">Donasi Sekarang</a>'; $html .= '</div>'; $html .= '</div></div>'; } wp_reset_postdata(); $html .= '</div>'; return $html; } /* ============================================================ * 6) SHORTCODE: [rn_donor_table] * ============================================================ */ add_shortcode( 'rn_donor_table', 'rn_sc_donor_table' ); function rn_sc_donor_table( $atts ) { $a = shortcode_atts( array( 'project' => 0, 'limit' => 150, 'title' => '', ), $atts, 'rn_donor_table' ); $pid = (int) $a['project'] ? (int) $a['project'] : rn_current_post_id(); if ( ! $pid ) { return ''; } global $wpdb; /* Fetch donations for this project from native 'donasi' post type */ $q = new WP_Query( array( 'post_type' => 'donasi', 'posts_per_page' => (int) $a['limit'], 'post_status' => 'publish', 'orderby' => 'date', 'order' => 'DESC', 'meta_query' => array( array( 'key' => 'project_pilihan', 'value' => (string) $pid, ), ), ) ); $donors = array(); if ( $q->have_posts() ) { while ( $q->have_posts() ) { $q->the_post(); $did = get_the_ID(); $tamp = get_post_meta( $did, 'tampilkan_nama_donatur', true ); $nama = get_post_meta( $did, 'nama_donatur', true ); $show = ( $tamp === 'ya' || $tamp === 'yes' || $tamp === '1' ); $display_nama = ( $show && ! empty( $nama ) ) ? $nama : 'Hamba Allah'; $amt = (float) get_post_meta( $did, 'jumlah_donasi', true ); $date = get_the_date( 'Y-m-d H:i:s', $did ); $donors[] = array( 'date' => $date, 'nama' => $display_nama, 'amount' => $amt, ); } wp_reset_postdata(); } /* Fallback to CCT table if empty */ if ( empty( $donors ) ) { $tbl = $wpdb->prefix . 'jet_cct_donasi'; if ( $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $tbl ) ) === $tbl ) { $rows = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$tbl} WHERE cct_status = 'publish' AND project_pilihan = %s ORDER BY cct_created DESC LIMIT %d", (string) $pid, (int) $a['limit'] ), ARRAY_A ); foreach ( (array) $rows as $r ) { $tamp = isset( $r['tampilkan_nama_donatur'] ) ? $r['tampilkan_nama_donatur'] : 'tidak'; $nama = isset( $r['nama_donatur'] ) ? trim( $r['nama_donatur'] ) : ''; $show = ( $tamp === 'ya' || $tamp === 'yes' || $tamp === '1' ); $donors[] = array( 'date' => isset( $r['cct_created'] ) ? $r['cct_created'] : '', 'nama' => ( $show && ! empty( $nama ) ) ? $nama : 'Hamba Allah', 'amount' => isset( $r['jumlah_donasi'] ) ? (float) $r['jumlah_donasi'] : 0, ); } } } $uid = 'rndt-' . wp_rand( 1000, 9999 ); $css = " #{$uid}{width:100%;border-collapse:collapse;font-family:'Quicksand',sans-serif;font-size:15px;margin-bottom:12px;} #{$uid} thead th{background:#f8fafc;color:#475569;font-weight:700;font-size:13px;text-transform:uppercase;letter-spacing:.04em;padding:12px 16px;border-bottom:2px solid #e2e8f0;text-align:left;} #{$uid} thead th.rn-th-amt{text-align:center;} #{$uid} tbody td{padding:14px 16px;border-bottom:1px solid #f1f5f9;color:#334155;vertical-align:middle;} #{$uid} tbody td.rn-td-amt{text-align:center;font-weight:700;color:#802829;font-family:'Poppins',sans-serif;} #{$uid} tbody tr:hover td{background:#fafafa;} #{$uid} .rn-avatar-anon{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border-radius:50%;background:#e2e8f0;color:#64748b;font-size:12px;font-weight:700;margin-right:8px;} .rndt-note{font-family:'Quicksand',sans-serif;font-size:14px;font-style:italic;color:#71717a;margin-top:8px;} "; $html = '<style>' . $css . '</style>'; $html .= '<div class="jet-dynamic-table-wrapper" style="overflow-x:auto;">'; $html .= '<table id="' . esc_attr( $uid ) . '" class="jet-dynamic-table">'; $html .= '<thead><tr class="jet-dynamic-table__row jet-dynamic-table__row--header">'; $html .= '<th style="width:25%;">Tanggal</th>'; $html .= '<th style="width:45%;">Donatur</th>'; $html .= '<th class="rn-th-amt" style="width:30%;">Jumlah Donasi</th>'; $html .= '</tr></thead>'; $html .= '<tbody class="jet-dynamic-table__body">'; if ( ! empty( $donors ) ) { foreach ( $donors as $d ) { $is_anon = ( $d['nama'] === 'Hamba Allah' ); $icon = $is_anon ? '<span class="rn-avatar-anon">HA</span>' : '<span class="rn-avatar-anon" style="background:#fce7f3;color:#be185d;">' . esc_html( mb_substr( $d['nama'], 0, 1 ) ) . '</span>'; $html .= '<tr class="jet-dynamic-table__row">'; $html .= '<td>' . esc_html( rn_format_date_id( $d['date'] ) ) . '</td>'; $html .= '<td>' . $icon . esc_html( $d['nama'] ) . '</td>'; $html .= '<td class="rn-td-amt">' . esc_html( rn_rupiah( $d['amount'] ) ) . '</td>'; $html .= '</tr>'; } } else { $html .= '<tr><td colspan="3" style="text-align:center;padding:24px;color:#94a3b8;font-style:italic;">Belum ada donatur yang tercatat untuk project ini. Jadilah yang pertama!</td></tr>'; } $html .= '</tbody></table>'; $html .= '<p class="rndt-note">* Terdapat beberapa nama donatur yang tidak ingin ditampilkan.</p>'; $html .= '</div>'; return $html; } /* ============================================================ * 7) SHORTCODE: [rn_donation_form] * ============================================================ */ add_shortcode( 'rn_donation_form', 'rn_sc_donation_form' ); function rn_sc_donation_form( $atts ) { $a = shortcode_atts( array( 'project' => 0 ), $atts, 'rn_donation_form' ); $pid = (int) $a['project'] ? (int) $a['project'] : rn_current_post_id(); $project_title = $pid ? get_the_title( $pid ) : 'Donasi Rumpun Nurani'; $uid = 'rnf-' . wp_rand( 1000, 9999 ); $action_url = esc_url( admin_url( 'admin-post.php' ) ); $css = " #{$uid}{display:flex;flex-direction:column;gap:18px;max-width:100%;font-family:'Quicksand',sans-serif;} #{$uid} .rnf-group{display:flex;flex-direction:column;gap:6px;} #{$uid} .rnf-label{font-size:16px;font-weight:600;color:#18181b;} #{$uid} .rnf-req{color:#dc2626;margin-left:3px;} #{$uid} .rnf-desc{font-size:13px;color:#71717a;margin-top:2px;} #{$uid} input[type=text],#{$uid} input[type=number],#{$uid} select{width:100%;padding:12px 16px;border:1px solid #d4d4d8;border-radius:10px;font-size:15px;font-family:'Quicksand',sans-serif;color:#18181b;background:#fff;box-sizing:border-box;transition:border-color .2s ease,box-shadow .2s ease;} #{$uid} input:focus,#{$uid} select:focus{outline:none;border-color:#802829;box-shadow:0 0 0 3px rgba(128,40,41,.14);} #{$uid} .rnf-quick-chips{display:flex;flex-wrap:wrap;gap:8px;margin-top:6px;} #{$uid} .rnf-chip{padding:6px 14px;border:1px solid #e4e4e7;background:#f4f4f5;border-radius:999px;font-size:13px;font-weight:600;color:#3f3f46;cursor:pointer;transition:all .2s ease;} #{$uid} .rnf-chip:hover{background:#802829;color:#fff;border-color:#802829;} #{$uid} button.rnf-submit{margin-top:6px;width:100%;padding:14px 20px;border:0;border-radius:10px;background:linear-gradient(90deg,#802829 0%,#dc2626 100%);color:#fff;font-family:'Poppins',sans-serif;font-weight:600;font-size:16px;cursor:pointer;transition:transform .2s ease,box-shadow .2s ease;} #{$uid} button.rnf-submit:hover{transform:translateY(-2px);box-shadow:0 8px 24px rgba(128,40,41,.25);} "; $html = '<style>' . $css . '</style>'; $html .= '<form id="' . esc_attr( $uid ) . '" class="rn-donation-form" method="post" action="' . $action_url . '">'; $html .= wp_nonce_field( 'rn_donation_action', 'rn_donation_nonce', true, false ); $html .= '<input type="hidden" name="action" value="rn_submit_donation">'; $html .= '<input type="hidden" name="project_id" value="' . esc_attr( $pid ) . '">'; $html .= '<input type="hidden" name="project_name" value="' . esc_attr( $project_title ) . '">'; /* WhatsApp */ $html .= '<div class="rnf-group">'; $html .= '<label class="rnf-label" for="' . $uid . '-wa">Nomor WhatsApp Anda<span class="rnf-req">*</span></label>'; $html .= '<input type="text" id="' . $uid . '-wa" name="nomor_wa" placeholder="08xxxxxxxxxx" required inputmode="tel">'; $html .= '</div>'; /* Jumlah Donasi */ $html .= '<div class="rnf-group">'; $html .= '<label class="rnf-label" for="' . $uid . '-jml">Rencana Jumlah Donasi<span class="rnf-req">*</span></label>'; $html .= '<input type="number" id="' . $uid . '-jml" name="jumlah_donasi" placeholder="Jumlah donasi dalam rupiah" min="10000" step="1000" required inputmode="numeric">'; $html .= '<div class="rnf-quick-chips">'; foreach ( array( 25000, 50000, 100000, 250000, 500000, 1000000 ) as $chip_val ) { $html .= '<button type="button" class="rnf-chip" onclick="document.getElementById(\'' . $uid . '-jml\').value=' . (int) $chip_val . ';">' . esc_html( rn_rupiah( $chip_val ) ) . '</button>'; } $html .= '</div>'; $html .= '<div class="rnf-desc">Isikan angka saja, tanpa tanda baca.</div>'; $html .= '</div>'; /* Tampilkan Nama */ $html .= '<div class="rnf-group">'; $html .= '<label class="rnf-label" for="' . $uid . '-tampil">Tampilkan Nama Donatur<span class="rnf-req">*</span></label>'; $html .= '<select id="' . $uid . '-tampil" name="tampilkan_nama" onchange="var g=document.getElementById(\'' . $uid . '-nama-group\');if(this.value===\'ya\'){g.style.display=\'flex\';}else{g.style.display=\'none\';}">'; $html .= '<option value="tidak" selected>Jangan tampilkan (Anonim / Hamba Allah)</option>'; $html .= '<option value="ya">Ya, tampilkan</option>'; $html .= '</select>'; $html .= '<div class="rnf-desc">Anda bisa menuliskan nama orang tua, komunitas, Hamba Allah atau siapa saja.</div>'; $html .= '</div>'; /* Nama Donatur (hidden by default unless 'ya' selected) */ $html .= '<div id="' . $uid . '-nama-group" class="rnf-group" style="display:none;">'; $html .= '<label class="rnf-label" for="' . $uid . '-nama">Nama Donatur<span class="rnf-req">*</span></label>'; $html .= '<input type="text" id="' . $uid . '-nama" name="nama_donatur" placeholder="Nama yang ingin dicantumkan">'; $html .= '</div>'; /* Submit Button */ $html .= '<button type="submit" class="rnf-submit">Kirim Komitmen Donasi</button>'; $html .= '</form>'; return $html; } /* Form Submission Handler */ add_action( 'admin_post_nopriv_rn_submit_donation', 'rn_handle_donation_submission' ); add_action( 'admin_post_rn_submit_donation', 'rn_handle_donation_submission' ); function rn_handle_donation_submission() { if ( ! isset( $_POST['rn_donation_nonce'] ) || ! wp_verify_nonce( $_POST['rn_donation_nonce'], 'rn_donation_action' ) ) { wp_die( 'Validasi keamanan gagal. Silakan coba kembali.' ); } $pid = isset( $_POST['project_id'] ) ? (int) $_POST['project_id'] : 0; $pname = isset( $_POST['project_name'] ) ? sanitize_text_field( wp_unslash( $_POST['project_name'] ) ) : ''; $wa = isset( $_POST['nomor_wa'] ) ? sanitize_text_field( wp_unslash( $_POST['nomor_wa'] ) ) : ''; $jml = isset( $_POST['jumlah_donasi'] ) ? (float) $_POST['jumlah_donasi'] : 0; $tamp = isset( $_POST['tampilkan_nama'] ) ? sanitize_text_field( wp_unslash( $_POST['tampilkan_nama'] ) ) : 'tidak'; $nama = isset( $_POST['nama_donatur'] ) ? sanitize_text_field( wp_unslash( $_POST['nama_donatur'] ) ) : ''; if ( $tamp === 'tidak' || empty( $nama ) ) { $nama = 'Hamba Allah'; } if ( $jml > 0 && $pid > 0 ) { /* 1. Insert into native 'donasi' CPT */ $post_id = wp_insert_post( array( 'post_type' => 'donasi', 'post_status' => 'publish', 'post_title' => $nama, ) ); if ( ! is_wp_error( $post_id ) ) { update_post_meta( $post_id, 'nama_donatur', $nama ); update_post_meta( $post_id, 'nomor_wa', $wa ); update_post_meta( $post_id, 'jumlah_donasi', $jml ); update_post_meta( $post_id, 'project_pilihan', (string) $pid ); update_post_meta( $post_id, 'tampilkan_nama_donatur', $tamp ); } /* 2. Dual-write to JetEngine CCT table if it still exists (graceful transition) */ global $wpdb; $tbl = $wpdb->prefix . 'jet_cct_donasi'; if ( $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $tbl ) ) === $tbl ) { $wpdb->insert( $tbl, array( 'cct_status' => 'publish', 'cct_author_id' => get_current_user_id() ? get_current_user_id() : 7, 'cct_created' => current_time( 'mysql' ), 'cct_modified' => current_time( 'mysql' ), 'jumlah_donasi' => (string) $jml, 'project_pilihan' => (string) $pid, 'tampilkan_nama_donatur' => $tamp, 'nama_donatur' => ( $tamp === 'ya' ) ? $nama : '', ) ); } } /* 3. Redirect to invoice / rekening donasi page */ $redirect_url = add_query_arg( array( 'jumlah_donasi' => (int) $jml, 'project_name' => rawurlencode( $pname ), ), home_url( '/rekening-donasi-rumpun-nurani/' ) ); wp_safe_redirect( $redirect_url ); exit; } /* ============================================================ * 8) REST API & DIAGNOSTICS (admin only) * ============================================================ */ add_action( 'rest_api_init', function() { register_rest_route( 'rn-native/v1', '/status', array( 'methods' => 'GET', 'permission_callback' => function() { return current_user_can( 'manage_options' ); }, 'callback' => 'rn_rest_status', ) ); register_rest_route( 'rn-native/v1', '/cct-dump', array( 'methods' => 'GET', 'permission_callback' => function() { return current_user_can( 'manage_options' ); }, 'callback' => 'rn_rest_cct_dump', ) ); register_rest_route( 'rn-native/v1', '/migrate-cct', array( 'methods' => 'POST', 'permission_callback' => function() { return current_user_can( 'manage_options' ); }, 'callback' => 'rn_rest_migrate_cct', ) ); register_rest_route( 'rn-native/v1', '/render', array( 'methods' => 'POST', 'permission_callback' => function() { return current_user_can( 'manage_options' ); }, 'callback' => 'rn_rest_render', ) ); } ); function rn_rest_status() { global $wpdb; $donasi_tbl = $wpdb->prefix . 'jet_cct_donasi'; return array( 'plugin' => RN_ND_VER, 'cct_table' => $donasi_tbl, 'cct_exists' => ( $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $donasi_tbl ) ) === $donasi_tbl ), 'cct_rows' => (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$donasi_tbl}" ), 'project_posts' => (int) wp_count_posts( 'project' )->publish, 'donasi_posts' => (int) wp_count_posts( 'donasi' )->publish, ); } function rn_rest_cct_dump() { global $wpdb; $tbl = $wpdb->prefix . 'jet_cct_donasi'; if ( $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $tbl ) ) !== $tbl ) { return new WP_Error( 'no_table', 'CCT table not found', array( 'status' => 404 ) ); } $cols = $wpdb->get_results( "SHOW COLUMNS FROM {$tbl}", ARRAY_A ); $rows = $wpdb->get_results( "SELECT * FROM {$tbl}", ARRAY_A ); return array( 'table' => $tbl, 'columns' => array_map( function( $c ) { return $c['Field'] . ' (' . $c['Type'] . ')'; }, $cols ), 'row_count' => count( $rows ), 'rows' => $rows, ); } function rn_rest_migrate_cct() { global $wpdb; $tbl = $wpdb->prefix . 'jet_cct_donasi'; if ( $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $tbl ) ) !== $tbl ) { return new WP_Error( 'no_table', 'CCT table not found', array( 'status' => 404 ) ); } $rows = $wpdb->get_results( "SELECT * FROM {$tbl}", ARRAY_A ); $created = 0; $skipped = 0; $log = array(); foreach ( (array) $rows as $r ) { $nama = isset( $r['nama_donatur'] ) ? trim( $r['nama_donatur'] ) : ''; $jml = isset( $r['jumlah_donasi'] ) ? (float) $r['jumlah_donasi'] : 0; $proj = isset( $r['project_pilihan'] ) ? $r['project_pilihan'] : ''; $tamp = isset( $r['tampilkan_nama_donatur'] ) ? $r['tampilkan_nama_donatur'] : 'tidak'; if ( ! $nama && ! $jml ) { $skipped++; continue; } $exists = get_posts( array( 'post_type' => 'donasi', 'post_status' => 'any', 'fields' => 'ids', 'numberposts' => 1, 'meta_query' => array( 'relation' => 'AND', array( 'key' => 'jumlah_donasi', 'value' => (string) $jml ), array( 'key' => 'project_pilihan', 'value' => (string) $proj ), ), ) ); if ( $exists ) { $skipped++; continue; } $pid = wp_insert_post( array( 'post_type' => 'donasi', 'post_status' => 'publish', 'post_title' => ( $tamp === 'ya' && ! empty( $nama ) ) ? $nama : 'Hamba Allah', 'post_date' => isset( $r['cct_created'] ) && $r['cct_created'] ? $r['cct_created'] : current_time( 'mysql' ), ), true ); if ( is_wp_error( $pid ) ) { continue; } update_post_meta( $pid, 'nama_donatur', ( $tamp === 'ya' && ! empty( $nama ) ) ? $nama : 'Hamba Allah' ); update_post_meta( $pid, 'jumlah_donasi', $jml ); update_post_meta( $pid, 'project_pilihan', (string) $proj ); update_post_meta( $pid, 'tampilkan_nama_donatur', $tamp ); update_post_meta( $pid, '_rn_migrated_from', 'jet_cct_donasi#' . ( isset( $r['_ID'] ) ? $r['_ID'] : '' ) ); $created++; $log[] = array( 'nama' => $nama, 'jml' => $jml, 'project' => $proj, 'new_id' => $pid ); } return array( 'created' => $created, 'skipped' => $skipped, 'total_cct' => count( (array) $rows ), 'sample' => array_slice( $log, 0, 20 ) ); } function rn_rest_render( $req ) { $sc = $req->get_param( 'shortcode' ); $id = (int) $req->get_param( 'post_id' ); if ( ! $sc ) { return new WP_Error( 'no_sc', 'shortcode required', array( 'status' => 400 ) ); } $prev = $GLOBALS['post'] ?? null; if ( $id ) { $GLOBALS['post'] = get_post( $id ); setup_postdata( $GLOBALS['post'] ); } $out = do_shortcode( $sc ); if ( $id ) { wp_reset_postdata(); } return array( 'len' => strlen( $out ), 'html' => $out ); } /* ============================================================ * 10) SHORTCODE: [rn_post_list] * Medium-style blog list dengan search, filter kategori, * seragam min-height, thumbnails, eksersi, tanggal, author. * ============================================================ */ add_shortcode( 'rn_post_list', 'rn_sc_post_list' ); function rn_sc_post_list( $atts ) { $a = shortcode_atts( array( 'count' => 6, 'page' => 1, 'category' => '', 'search' => '', 'orderby' => 'date', 'order' => 'DESC', 'layout' => 'grid', 'let_filter' => 'yes', 'let_search' => 'yes', 'let_categories' => 'yes', ), $atts, 'rn_post_list' ); $args = array( 'post_type' => 'post', 'posts_per_page' => (int) $a['count'], 'post_status' => 'publish', 'orderby' => $a['orderby'], 'order' => $a['order'], 'paged' => max( 1, (int) $a['page'] ), ); if ( ! empty( $a['category'] ) ) { $args['cat'] = (int) $a['category']; } if ( ! empty( $a['search'] ) ) { $args['s'] = sanitize_text_field( $a['search'] ); } $q = new WP_Query( $args ); $total_posts = (int) $q->found_posts; $total_pages = max( 1, (int) ceil( $total_posts / max( 1, $a['count'] ) ) ); $uid = 'rnpl-' . wp_rand( 1000, 9999 ); $categories = get_categories( array( 'hide_empty' => true, 'number' => 12 ) ); $css = " #{$uid} .rnpl-controls{display:flex;flex-direction:column;gap:14px;margin-bottom:28px;padding:20px 24px;background:#fafafa;border:1px solid #e2e8f0;border-radius:18px;} #{$uid} .rnpl-controls-top{display:flex;gap:12px;flex-wrap:wrap;align-items:center;} #{$uid} .rnpl-search{flex:1;min-width:220px;position:relative;} #{$uid} .rnpl-search input{width:100%;padding:12px 14px 12px 40px;border:1px solid #cbd5e1;border-radius:12px;font-size:15px;font-family:inherit;background:#fff;box-sizing:border-box;} #{$uid} .rnpl-search input:focus{outline:none;border-color:#802829;box-shadow:0 0 0 3px rgba(128,40,41,.12);} #{$uid} .rnpl-search svg{position:absolute;left:12px;top:50%;transform:translateY(-50%);width:18px;height:18px;color:#94a3b8;} #{$uid} .rnpl-search-btn{padding:12px 20px;background:#802829;color:#fff;border:0;border-radius:12px;font-weight:600;font-size:14px;cursor:pointer;font-family:inherit;white-space:nowrap;transition:opacity .2s;} #{$uid} .rnpl-search-btn:hover{opacity:.9;} #{$uid} .rnpl-cats{display:flex;gap:8px;flex-wrap:wrap;} #{$uid} .rnpl-cat{padding:8px 14px;border-radius:999px;font-size:13px;font-weight:600;font-family:inherit;color:#475569;text-decoration:none;background:#fff;border:1px solid #e2e8f0;transition:all .2s ease;cursor:pointer;} #{$uid} .rnpl-cat:hover{background:#802829;color:#fff;border-color:#802829;} #{$uid} .rnpl-cat.active{background:#802829;color:#fff;border-color:#802829;} #{$uid} .rnpl-cat.all{padding:8px 16px;background:#802829;color:#fff;border-color:#802829;} #{$uid} .rnpl-cats .rnpl-cat-all{margin-right:auto;background:#802829;color:#fff;border-color:#802829;font-weight:700;padding:8px 18px;} #{$uid} .rnpl-results-info{font-size:13px;color:#64748b;} #{$uid} .rnpl-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:28px;} #{$uid} .rnpl-card{display:flex;flex-direction:column;background:#fff;border:1px solid #f1f5f9;border-radius:20px;overflow:hidden;text-decoration:none;transition:transform .3s ease,box-shadow .3s ease;min-height:420px;} #{$uid} .rnpl-card:hover{transform:translateY(-6px);box-shadow:0 18px 40px rgba(15,23,42,.12);} #{$uid} .rnpl-thumb{aspect-ratio:16/9;overflow:hidden;background:#f8fafc;position:relative;} #{$uid} .rnpl-thumb img{width:100%;height:100%;object-fit:cover;display:block;margin:0;transition:transform .35s ease;} #{$uid} .rnpl-card:hover .rnpl-thumb img{transform:scale(1.04);} #{$uid} .rnpl-thumb .rnpl-badge{position:absolute;top:14px;left:14px;background:linear-gradient(135deg,#802829 0%, #dc2626 100%);color:#fff;padding:4px 12px;font-size:11px;font-weight:700;border-radius:20px;letter-spacing:.04em;text-transform:uppercase;} #{$uid} .rnpl-body{padding:22px 24px 26px;display:flex;flex-direction:column;gap:12px;flex:1;} #{$uid} .rnpl-meta{font-size:12px;color:#94a3b8;display:flex;align-items:center;gap:12px;} #{$uid} .rnpl-meta .rnpl-author{color:#802829;font-weight:600;} #{$uid} .rnpl-meta .rnpl-dot{width:3px;height:3px;background:#cbd5e1;border-radius:50%;} #{$uid} .rnpl-title{font-family:'Poppins',sans-serif;font-size:17px;line-height:1.45;font-weight:700;color:#0f172a;margin:0;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;} #{$uid} .rnpl-excerpt{font-family:'Quicksand',sans-serif;font-size:14px;line-height:1.7;color:#475569;flex:1;display:-webkit-box;-webkit-line-clamp:4;-webkit-box-orient:vertical;overflow:hidden;} #{$uid} .rnpl-footer{margin-top:auto;padding-top:16px;border-top:1px solid #f1f5f9;display:flex;align-items:center;gap:8px;color:#802829;font-size:13px;font-weight:600;text-decoration:none;transition:gap .2s;} #{$uid} .rnpl-card:hover .rnpl-footer{gap:12px;} #{$uid} .rnpl-footer svg{width:15px;height:15px;} #{$uid} .rnpl-pagination{display:flex;justify-content:center;gap:8px;margin-top:40px;flex-wrap:wrap;} #{$uid} .rnpl-page{padding:10px 16px;border:1px solid #e2e8f0;border-radius:10px;background:#fff;font-size:14px;font-weight:600;color:#475569;text-decoration:none;transition:all .2s;min-width:44px;text-align:center;} #{$uid} .rnpl-page:hover{background:#802829;color:#fff;border-color:#802829;} #{$uid} .rnpl-page.current{background:#802829;color:#fff;border-color:#802829;} #{$uid} .rnpl-page.ellipsis{background:transparent;border:0;color:#94a3b8;padding:10px;letter-spacing:2px;} #{$uid} .rnpl-no-results{text-align:center;padding:60px 24px;background:#fafafa;border:1px solid #f1f5f9;border-radius:20px;} #{$uid} .rnpl-no-results-icon{font-size:44px;display:block;margin-bottom:12px;} #{$uid} .rnpl-no-results-text{font-size:16px;font-weight:600;color:#334155;} @media(max-width:640px){ #{$uid} .rnpl-grid{grid-template-columns:1fr;} #{$uid} .rnpl-card{min-height:auto;} #{$uid} .rnpl-controls-top{flex-direction:column;} #{$uid} .rnpl-search{min-width:100%;} #{$uid} .rnpl-search-btn{width:100%;} } "; $html = ''; $html .= '
'; /* ============================================================ FILTER & SEARCH CONTROLS ============================================================ */ if ( 'yes' === $a['let_filter'] ) { $html .= '
'; /* Search row */ if ( 'yes' === $a['let_search'] ) { $search_val = sanitize_text_field( filter_input( INPUT_GET, 'rn_q', FILTER_SANITIZE_STRING ) ?: '' ); $html .= '
'; $html .= ''; // preserve category filter if present $cur_cat = sanitize_text_field( filter_input( INPUT_GET, 'rn_cat', FILTER_SANITIZE_STRING ) ?: '' ); if ( $cur_cat ) { $html .= ''; } $html .= ''; $html .= ''; $html .= '
'; } /* Category chips */ if ( 'yes' === $a['let_categories'] && ! empty( $categories ) ) { $cur_cat = sanitize_text_field( filter_input( INPUT_GET, 'rn_cat', FILTER_SANITIZE_STRING ) ?: '' ); $search_val = sanitize_text_field( filter_input( INPUT_GET, 'rn_q', FILTER_SANITIZE_STRING ) ?: '' ); $base_url = remove_query_arg( array( 'rn_cat', 'rn_q', 'paged' ) ); $html .= '
'; $html .= 'Semua'; foreach ( $categories as $cat ) { $active_cls = ( $cur_cat == $cat->term_id ) ? ' active' : ''; $cat_url = add_query_arg( array( 'rn_cat' => $cat->term_id, 'rn_q' => $search_val ), $base_url ); $html .= '' . esc_html( $cat->name ) . ''; } $html .= '
'; } /* results info */ if ( $total_posts > 0 ) { $html .= '
Menampilkan ' . $q->post_count . ' dari ' . $total_posts . ' artikel
'; } $html .= '
'; } /* ============================================================ POST GRID ============================================================ */ if ( $q->have_posts() ) { $html .= '
'; while ( $q->have_posts() ) { $q->the_post(); $pid = get_the_ID(); $link = get_permalink( $pid ); $thumb = get_the_post_thumbnail( $pid, 'large', array( 'loading' => 'lazy', 'alt' => esc_attr( get_the_title() ) ) ); $excerpt = get_the_excerpt( $pid ); $date_str = rn_format_date_id( get_the_date( 'Y-m-d H:i:s', $pid ) ); $author = 'Tim Media RN'; $cats = get_the_category( $pid ); $cat_name = ! empty( $cats ) ? esc_html( strtoupper( $cats[0]->name ) ) : 'BERITA'; if ( ! $thumb ) { $thumb = '' . esc_attr( get_the_title() ) . ''; } $html .= ''; $html .= '
'; $html .= $thumb; $html .= '' . $cat_name . ''; $html .= '
'; $html .= '
'; $html .= '
'; $html .= '' . esc_html( $author ) . ''; $html .= ''; $html .= '' . esc_html( $date_str ) . ''; $html .= '
'; $html .= '

' . esc_html( get_the_title() ) . '

'; if ( ! empty( $excerpt ) ) { $html .= '
' . esc_html( wp_strip_all_tags( $excerpt ) ) . '
'; } $html .= ''; $html .= '
'; } wp_reset_postdata(); $html .= '
'; /* ============================================================ PAGINATION (berdasarkan total posts) ============================================================ */ if ( $total_pages > 1 ) { $html .= '
'; $cur_page = max( 1, (int) $a['page'] ); $cur_cat = sanitize_text_field( filter_input( INPUT_GET, 'rn_cat', FILTER_SANITIZE_STRING ) ?: '' ); $search_val = sanitize_text_field( filter_input( INPUT_GET, 'rn_q', FILTER_SANITIZE_STRING ) ?: '' ); $base_url = remove_query_arg( array( 'paged', 'rn_cat', 'rn_q' ) ); $visible = 5; $start = max( 1, $cur_page - 2 ); $end = min( $total_pages, $start + $visible - 1 ); $start = max( 1, $end - $visible + 1 ); for ( $pg = $start; $pg <= $end; $pg++ ) { $active = ( $pg === $cur_page ) ? ' current' : ''; $url = add_query_arg( array( 'rn_cat' => $cur_cat, 'rn_q' => $search_val, 'paged' => $pg ), $base_url ); $html .= '' . $pg . ''; } if ( $end < $total_pages ) { $html .= ''; $html .= '' . $total_pages . ''; } $html .= '
'; } } else { /* No results */ $html .= '
'; $html .= '📭'; $html .= '

Belum ada artikel untuk kategori ini.

'; $html .= '
'; } $html .= '
'; return $html; } /* ============================================================ * 9) KEAMANAN * ============================================================ */ add_filter( 'rest_endpoints', function( $endpoints ) { if ( ! is_user_logged_in() ) { unset( $endpoints['/wp/v2/users'] ); unset( $endpoints['/wp/v2/users/(?P<id>[\d]+)'] ); } return $endpoints; } ); Comments on: Udah Kerja, Belum Nikah… Kok Jadi ATM Lebaran? https://rumpunnurani.org/udah-kerja-belum-nikah-kok-jadi-atm-lebaran/ Nurani Untuk Negeri Tue, 25 Mar 2025 15:25:34 +0000 hourly 1 https://wordpress.org/?v=7.1.1