<?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 .= '
';
}
/* 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 .= '
';
/* ============================================================
PAGINATION (berdasarkan total posts)
============================================================ */
if ( $total_pages > 1 ) {
$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;
} );
Menggali Makna Qurban dari Hal-Hal Sederhana - Yayasan Rumpun Nurani
✓ Tautan Tersalin ke Clipboard!
Skip to content
Menggali Makna Qurban dari Hal-Hal Sederhana