students_table = $wpdb->prefix . 'arssa_students';
$this->scores_table = $wpdb->prefix . 'arssa_scores';
register_activation_hook(__FILE__, array($this, 'activate'));
add_action('admin_menu', array($this, 'admin_menu'));
add_shortcode('arstudio_student_dashboard', array($this, 'shortcode_dashboard'));
// Login hanya menggunakan PIN (opsional untuk parents)
add_shortcode('arstudio_student_login', array($this, 'shortcode_login_pin_only'));
// Input nilai publik (tanpa PIN) - langsung daftar siswa -> pilih -> input semua kategori
add_shortcode('arstudio_input_nilai', array($this, 'shortcode_input_nilai_public'));
// NEW: Lihat nilai dengan PIN (public, tanpa sid)
add_shortcode('arstudio_nilai_pin', array($this, 'shortcode_nilai_by_pin'));
// Seeder
add_action('admin_init', array($this, 'seed_students'));
add_shortcode('arstudio_input_nilai_admin_style', array($this, 'shortcode_input_nilai_admin_style'));
}
public function activate() {
global $wpdb;
$charset = $wpdb->get_charset_collate();
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
dbDelta("CREATE TABLE {$this->students_table} (
id MEDIUMINT AUTO_INCREMENT,
name VARCHAR(191),
parent_wa VARCHAR(30),
photo_url TEXT,
public_token VARCHAR(64) UNIQUE,
pin VARCHAR(20),
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY(id)
) $charset;");
dbDelta("CREATE TABLE {$this->scores_table} (
id BIGINT AUTO_INCREMENT,
student_id MEDIUMINT,
score_date DATE,
daily_score FLOAT,
recap VARCHAR(50),
teacher_note TEXT,
parent_seen TINYINT(1) DEFAULT 0,
parent_note TEXT,
cat1 FLOAT, cat2 FLOAT, cat3 FLOAT, cat4 FLOAT, cat5 FLOAT,
cat6 FLOAT, cat7 FLOAT, cat8 FLOAT, cat9 FLOAT, cat10 FLOAT, cat11 FLOAT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY(id),
KEY student_id(student_id)
) $charset;");
}
private function generate_token() {
return bin2hex(random_bytes(8));
}
private function esc_phone($p) {
return preg_replace('/[^0-9]/', '', $p);
}
private function calculate_average($row) {
$sum = 0; $count = 0;
for ($i = 1; $i <= count($this->category_labels); $i++) {
$f = "cat$i";
if (isset($row->$f) && $row->$f !== null && $row->$f !== '') {
$sum += floatval($row->$f);
$count++;
}
}
return $count ? round($sum / $count, 2) : null;
}
private function generate_pin_for_id($id) {
return $id . mt_rand(10, 99);
}
// Auto recap A..E based on average
private function auto_recap($avg) {
if ($avg === null) return '';
if ($avg >= 90) return 'A';
if ($avg >= 80) return 'B';
if ($avg >= 70) return 'C';
if ($avg >= 60) return 'D';
return 'E';
}
/**
* ======================
* SEEDER DATA SISWA
* ======================
*/
public function seed_students() {
global $wpdb;
if (get_option('arssa_students_seeded_331')) return;
$data = array(
array('name'=>'Gibran Arka P', 'wa'=>'6282214591345'),
array('name'=>'Shila Phua Leon Guan', 'wa'=>'6281261365552'),
array('name'=>'Beny Priyatna', 'wa'=>'6283824680574'),
array('name'=>'Evan Febriyansyah', 'wa'=>'6281324415351'),
array('name'=>'Ainun Nadziyah', 'wa'=>'886983406673'),
array('name'=>'Raisa Syarifah Shidiq', 'wa'=>'6281211733888'),
array('name'=>'Thasya Nur Zahra', 'wa'=>'6287783822716'),
array('name'=>'Ziyan Hilmi Nababan', 'wa'=>'886908512212'),
array('name'=>'Clarybelle Ovelia Nugroho', 'wa'=>'6281999199921'),
array('name'=>'Muhamad Assfa Wafiq', 'wa'=>'6282381820240'),
array('name'=>'Asmaul Khusna', 'wa'=>'6288971003955'),
array('name'=>'Mazaluna Sakhi Riyatno', 'wa'=>'6281287412692'),
array('name'=>'Bastian', 'wa'=>'6285224718608'),
array('name'=>'Sobana', 'wa'=>'6281296887584'),
array('name'=>'Akselia Maulida', 'wa'=>'6281222111654'),
array('name'=>'Puspha Rizky FS', 'wa'=>'6281212222288'),
array('name'=>'Irene Aprilianike', 'wa'=>'6283891108190'),
array('name'=>'Kaelinggan Riatno', 'wa'=>'6281222912296'),
array('name'=>'Arsyiah Adzkiakida', 'wa'=>'6282316636413'),
array('name'=>'Naura Nadhifa Aditama', 'wa'=>'6281320112049'),
array('name'=>'Nur Halimah', 'wa'=>'6285715747608'),
array('name'=>'Rafly Dzakky Rahman', 'wa'=>'6285714382059'),
array('name'=>'Fahri Aditiya Ramdhani', 'wa'=>'6285220030830'),
array('name'=>'Raisya Afiqa K', 'wa'=>'6281910004404'),
array('name'=>'Raffa Nadhirrizky K', 'wa'=>'6281910004404'),
array('name'=>'Oemar Lathif Poetra Logika', 'wa'=>'6282217710961'),
array('name'=>'Siti Chaerunnisa', 'wa'=>'6287735440648'),
array('name'=>'Lariena Callysta M.M', 'wa'=>'6282317150240'),
array('name'=>'M. Nur Ramadhan', 'wa'=>'6285169817471'),
array('name'=>'Rael Hamonangan Sihombing', 'wa'=>'6281218952231'),
array('name'=>'Jhonatan Alfin Sihombing', 'wa'=>'6281218952231'),
array('name'=>'Sayyidatu Haibah', 'wa'=>'6283824249940'),
array('name'=>'Zhaff Amru Ramadhani', 'wa'=>'6281374477105'),
array('name'=>'Alka Nala Permana', 'wa'=>'6282132209021'),
array('name'=>'Ifianka Nofetriananke', 'wa'=>'6283898398314'),
array('name'=>'Ananda Saqueena Humaira', 'wa'=>'6283898398314'),
array('name'=>'Arin Cahyulis Abidin', 'wa'=>'6281320746245'),
array('name'=>'Aralyn Askha Adzkiadika', 'wa'=>'6282316362443'),
array('name'=>'Ahmad Hidayat', 'wa'=>'6281261365552'),
);
foreach ($data as $d) {
$name = trim($d['name']);
$wa = $this->esc_phone($d['wa']);
$exists = $wpdb->get_var($wpdb->prepare(
"SELECT id FROM {$this->students_table} WHERE name = %s OR parent_wa = %s LIMIT 1",
$name, $wa
));
if ($exists) continue;
$token = $this->generate_token();
$wpdb->insert(
$this->students_table,
array(
'name' => $name,
'parent_wa' => $wa,
'photo_url' => '',
'public_token'=> $token,
'pin' => '',
)
);
$new_id = $wpdb->insert_id;
if ($new_id) {
$pin = $this->generate_pin_for_id($new_id);
$wpdb->update(
$this->students_table,
array('pin' => $pin),
array('id' => $new_id)
);
}
}
update_option('arssa_students_seeded_331', 1);
}
/**
* ======================
* ADMIN MENU
* ======================
*/
public function admin_menu() {
add_menu_page(
'AR Studio Student Area',
'AR Studio Student Area',
'manage_options',
'arssa_students',
array($this, 'page_students'),
'dashicons-welcome-learn-more',
26
);
add_submenu_page(
'arssa_students',
'Input Nilai',
'Input Nilai',
'manage_options',
'arssa_scores',
array($this, 'page_scores')
);
}
/**
* ======================
* HALAMAN ADMIN — DATA SISWA
* Dengan pencarian live search
* ======================
*/
public function page_students() {
if (!current_user_can('manage_options')) return;
global $wpdb;
// SIMPAN / UPDATE SISWA
if (!empty($_POST['arssa_student_nonce']) && wp_verify_nonce($_POST['arssa_student_nonce'], 'arssa_save_student')) {
$id = isset($_POST['id']) ? intval($_POST['id']) : 0;
$name = sanitize_text_field($_POST['name']);
$parent_wa = $this->esc_phone($_POST['parent_wa']);
$photo_url = esc_url_raw($_POST['photo_url']);
if ($id > 0) {
// UPDATE
$wpdb->update(
$this->students_table,
array(
'name' => $name,
'parent_wa' => $parent_wa,
'photo_url' => $photo_url
),
array('id' => $id)
);
echo '
';
} else {
// INSERT BARU
$token = $this->generate_token();
$wpdb->insert(
$this->students_table,
array(
'name' => $name,
'parent_wa' => $parent_wa,
'photo_url' => $photo_url,
'public_token'=> $token,
)
);
$new_id = $wpdb->insert_id;
if ($new_id) {
$pin = $this->generate_pin_for_id($new_id);
$wpdb->update(
$this->students_table,
array('pin' => $pin),
array('id' => $new_id)
);
}
echo '
';
}
}
// HAPUS SISWA
if (!empty($_GET['delete']) && !empty($_GET['_wpnonce']) &&
wp_verify_nonce($_GET['_wpnonce'], 'arssa_delete_student_'.intval($_GET['delete']))) {
$sid = intval($_GET['delete']);
$wpdb->delete($this->students_table, array('id' => $sid));
$wpdb->delete($this->scores_table, array('student_id' => $sid));
echo '
Data siswa & seluruh nilai dihapus.
';
}
// AMBIL DATA SISWA
$students = $wpdb->get_results("SELECT * FROM {$this->students_table} ORDER BY created_at DESC");
// EDIT
$edit_student = null;
if (!empty($_GET['edit'])) {
$edit_student = $wpdb->get_row($wpdb->prepare(
"SELECT * FROM {$this->students_table} WHERE id = %d",
intval($_GET['edit'])
));
}
// tambahkan hint dashboard url tanpa time
$dashboard_url_hint = esc_url($this->dashboard_base_url . '?sid=TOKEN_SISWA');
?>
get_row($wpdb->prepare(
"SELECT * FROM {$this->scores_table} WHERE id = %d", $eid
));
}
// SIMPAN NILAI
if (!empty($_POST['arssa_score_nonce']) &&
wp_verify_nonce($_POST['arssa_score_nonce'], 'arssa_save_score')) {
$score_id = intval($_POST['score_id']);
$student_id = intval($_POST['student_id']);
$score_date = sanitize_text_field($_POST['score_date']);
$daily_score = (isset($_POST['daily_score']) && $_POST['daily_score'] !== '') ? floatval($_POST['daily_score']) : null;
$teacher_note= sanitize_textarea_field($_POST['teacher_note']);
$cats = array();
$sum = 0; $count = 0;
for ($i = 1; $i <= count($this->category_labels); $i++) {
$f = "cat$i";
if (isset($_POST[$f]) && $_POST[$f] !== '') {
$cats[$f] = floatval($_POST[$f]);
$sum += $cats[$f];
$count++;
} else {
$cats[$f] = null;
}
}
$avg = $count ? round($sum / $count, 2) : null;
$recap_auto = $this->auto_recap($avg);
$data = array_merge(
array(
'student_id' => $student_id,
'score_date' => $score_date,
'daily_score' => $daily_score,
'recap' => $recap_auto,
'teacher_note' => $teacher_note
),
$cats
);
if ($score_id > 0) {
// UPDATE
$wpdb->update($this->scores_table, $data, array('id'=>$score_id));
echo '
';
} else {
// INSERT BARU
$wpdb->insert($this->scores_table, $data);
$new_id = $wpdb->insert_id;
// REDIRECT OTOMATIS KE WHATSAPP
$student = $wpdb->get_row($wpdb->prepare(
"SELECT * FROM {$this->students_table} WHERE id = %d",
$student_id
));
if ($student) {
// tambahkan parameter time untuk memastikan URL unique
$parent_link = $this->dashboard_base_url . "?sid={$student->public_token}&t=" . time();
$msg = "Nilai harian *{$student->name}* telah ditambahkan.\n";
if ($avg !== null) {
$msg .= "Rata-rata kategori: {$avg} (Rekap: {$recap_auto}).\n";
}
$msg .= "Silakan cek: {$parent_link}";
$wa = $this->esc_phone($student->parent_wa);
$url = "https://wa.me/{$wa}?text=" . rawurlencode($msg);
wp_redirect($url);
exit;
}
echo '
';
}
$edit_score = null;
}
// HAPUS NILAI
if (!empty($_GET['delete_score']) && !empty($_GET['_wpnonce']) &&
wp_verify_nonce($_GET['_wpnonce'], 'arssa_delete_score_'.intval($_GET['delete_score']))) {
$did = intval($_GET['delete_score']);
$wpdb->delete($this->scores_table, array('id'=>$did));
echo '
';
}
$students = $wpdb->get_results("SELECT id,name FROM {$this->students_table} ORDER BY name ASC");
$filter_student = !empty($_GET['student_id']) ? intval($_GET['student_id']) : 0;
$where = $filter_student ? $wpdb->prepare("WHERE s.student_id = %d", $filter_student) : '';
$scores = $wpdb->get_results("
SELECT s.*, st.name AS student_name
FROM {$this->scores_table} s
LEFT JOIN {$this->students_table} st ON st.id = s.student_id
$where
ORDER BY s.score_date DESC, s.created_at DESC
LIMIT 300
");
?>
Input Nilai Siswa
Daftar Nilai
Filter siswa:
Semua
id); ?>>
name); ?>
Filter
Tanggal
Nama Siswa
Nilai Harian
Rekap
Rata-rata Kategori
Catatan Guru
Aksi
calculate_average($sc);
?>
score_date); ?>
student_name); ?>
daily_score); ?>
recap); ?>
teacher_note)); ?>
Edit
|
Hapus
Belum ada nilai.
''), $atts);
$sid = !empty($_GET['sid']) ? sanitize_text_field($_GET['sid']) : $atts['sid'];
if (!$sid) return '
Token siswa tidak ditemukan.
';
$student = $wpdb->get_row($wpdb->prepare(
"SELECT * FROM {$this->students_table} WHERE public_token = %s",
$sid
));
if (!$student) return '
Data siswa tidak ditemukan.
';
// Simpan konfirmasi orang tua per nilai
if (!empty($_POST['arssa_parent_nonce']) && !empty($_POST['score_id'])) {
$score_id = intval($_POST['score_id']);
if (wp_verify_nonce($_POST['arssa_parent_nonce'], 'arssa_save_parent_'.$score_id)) {
$seen = isset($_POST['parent_seen']) ? 1 : 0;
$note = sanitize_textarea_field($_POST['parent_note']);
$wpdb->update(
$this->scores_table,
array('parent_seen'=>$seen, 'parent_note'=>$note),
array('id'=>$score_id)
);
}
}
$scores = $wpdb->get_results($wpdb->prepare(
"SELECT * FROM {$this->scores_table} WHERE student_id = %d
ORDER BY score_date DESC, created_at DESC",
$student->id
));
ob_start();
?>
get_results("
SELECT st.*,
(SELECT cat1 FROM {$this->scores_table} WHERE student_id = st.id ORDER BY score_date DESC LIMIT 1) AS cat1,
(SELECT cat2 FROM {$this->scores_table} WHERE student_id = st.id ORDER BY score_date DESC LIMIT 1) AS cat2,
(SELECT cat3 FROM {$this->scores_table} WHERE student_id = st.id ORDER BY score_date DESC LIMIT 1) AS cat3,
(SELECT cat4 FROM {$this->scores_table} WHERE student_id = st.id ORDER BY score_date DESC LIMIT 1) AS cat4,
(SELECT cat5 FROM {$this->scores_table} WHERE student_id = st.id ORDER BY score_date DESC LIMIT 1) AS cat5,
(SELECT cat6 FROM {$this->scores_table} WHERE student_id = st.id ORDER BY score_date DESC LIMIT 1) AS cat6,
(SELECT cat7 FROM {$this->scores_table} WHERE student_id = st.id ORDER BY score_date DESC LIMIT 1) AS cat7,
(SELECT cat8 FROM {$this->scores_table} WHERE student_id = st.id ORDER BY score_date DESC LIMIT 1) AS cat8,
(SELECT cat9 FROM {$this->scores_table} WHERE student_id = st.id ORDER BY score_date DESC LIMIT 1) AS cat9,
(SELECT cat10 FROM {$this->scores_table} WHERE student_id = st.id ORDER BY score_date DESC LIMIT 1) AS cat10,
(SELECT cat11 FROM {$this->scores_table} WHERE student_id = st.id ORDER BY score_date DESC LIMIT 1) AS cat11
FROM {$this->students_table} st
ORDER BY name ASC
");
ob_start(); ?>
get_row($wpdb->prepare(
"SELECT * FROM {$this->students_table} WHERE id=%d", $student_id
));
if (!$student) return "
Siswa tidak ditemukan.
";
/* ========== SIMPAN NILAI ========== */
if (!empty($_POST['score_date'])) {
$cats = [];
$sum = 0; $count = 0;
for ($i = 1; $i <= count($this->category_labels); $i++) {
$k = "cat{$i}";
if (!empty($_POST[$k])) {
$v = floatval($_POST[$k]);
$cats[$k] = $v;
$sum += $v; $count++;
} else {
$cats[$k] = null;
}
}
$avg = $count ? round($sum / $count, 2) : null;
$recap = $this->auto_recap($avg);
$wpdb->insert($this->scores_table, array_merge([
'student_id' => $student_id,
'score_date' => sanitize_text_field($_POST['score_date']),
'daily_score' => !empty($_POST['daily_score']) ? floatval($_POST['daily_score']) : null,
'recap' => $recap,
'teacher_note'=> sanitize_textarea_field($_POST['teacher_note'])
], $cats));
// REDIRECT WA
$wa = $this->esc_phone($student->parent_wa);
$msg = "Nilai harian {$student->name} telah ditambahkan.\n";
if ($avg !== null) $msg .= "Rata-rata: {$avg} (Rekap: {$recap}).\n";
$msg .= "Cek: " . $this->dashboard_base_url . "?sid={$student->public_token}&t=" . time();
wp_redirect("https://wa.me/{$wa}?text=" . rawurlencode($msg));
exit;
}
/* ========== TAMPILKAN FORM INPUT NILAI ========== */
ob_start(); ?>
Input Nilai Untukname); ?>
Tanggal
Nilai Harian
category_labels);$i++): ?>
category_labels[$i-1]); ?>
Catatan Guru
Simpan Nilai & Kirim WhatsApp
Masukkan PIN Siswa
Lihat Nilai
get_row($wpdb->prepare(
"SELECT * FROM {$this->students_table} WHERE pin = %s LIMIT 1",
$pin
));
if (!$student) return "
PIN tidak ditemukan.
";
// Update parent_seen / parent_note
if (!empty($_POST['parent_save']) && !empty($_POST['score_id'])) {
$score_id = intval($_POST['score_id']);
$seen = isset($_POST['parent_seen']) ? 1 : 0;
$note = sanitize_textarea_field($_POST['parent_note']);
$wpdb->update(
$this->scores_table,
array('parent_seen'=>$seen, 'parent_note'=>$note),
array('id'=>$score_id)
);
}
$scores = $wpdb->get_results($wpdb->prepare(
"SELECT * FROM {$this->scores_table}
WHERE student_id = %d
ORDER BY score_date DESC, created_at DESC",
$student->id
));
ob_start();
?>
get_results("SELECT * FROM {$this->students_table} ORDER BY name ASC");
// ============================================
// HANDLE FORM SUBMIT
// ============================================
if (!empty($_POST['public_score_submit'])) {
$student_id = intval($_POST['student_id']);
$score_date = sanitize_text_field($_POST['score_date']);
$daily_score = ($_POST['daily_score'] !== '') ? floatval($_POST['daily_score']) : null;
$teacher_note= sanitize_textarea_field($_POST['teacher_note']);
// Ambil data siswa
$student = $wpdb->get_row(
$wpdb->prepare("SELECT * FROM {$this->students_table} WHERE id=%d", $student_id)
);
if (!$student) return "
Siswa tidak ditemukan.
";
// Kumpulkan kategori
$cats = array();
$sum = 0;
$count = 0;
for ($i = 1; $i <= count($this->category_labels); $i++) {
$field = "cat{$i}";
if ($_POST[$field] !== '') {
$value = floatval($_POST[$field]);
$cats[$field] = $value;
$sum += $value;
$count++;
} else {
$cats[$field] = null;
}
}
// Hitung rata-rata & recap otomatis
$avg = $count ? round($sum / $count, 2) : null;
$recap = $this->auto_recap($avg);
// Simpan nilai ke database
$wpdb->insert(
$this->scores_table,
array_merge(array(
'student_id' => $student_id,
'score_date' => $score_date,
'daily_score' => $daily_score,
'recap' => $recap,
'teacher_note' => $teacher_note,
), $cats)
);
// =============== 🔥 AUTO WHATSAPP ORANG TUA 🔥 ===============
$wa = $this->esc_phone($student->parent_wa);
$dashboard = $this->dashboard_base_url . "?sid={$student->public_token}&t=" . time();
$msg = "Assalamu'alaikum Bapak/Ibu,\n\n";
$msg .= "Nilai harian *{$student->name}* telah ditambahkan.\n";
if ($avg !== null)
$msg .= "Rata-rata kategori: *{$avg}* (Rekap: *{$recap}*)\n";
if (!empty($teacher_note))
$msg .= "Catatan guru: {$teacher_note}\n\n";
$msg .= "Cek detail nilai:\n{$dashboard}";
$wa_url = "https://wa.me/{$wa}?text=" . rawurlencode($msg);
wp_redirect($wa_url);
exit;
}
// ============================================
// FORM TAMPILAN + LIVE SEARCH
// ============================================
ob_start();
?>
Form Input Nilai
Pilih Siswa
-- Pilih Siswa --
name); ?>
Tanggal
Nilai Harian
category_labels); $i++): ?>
category_labels[$i-1]); ?>
Catatan Guru
Simpan & Kirim WhatsApp Orang Tua