Like Permissions, Restrictions & Completing the LikeBox
Ringkasan
Video ini membahas penambahan permission checks dan restrictions pada custom like endpoint: login check, nonce, limit 1 like per professor, validasi post type. Lalu kita mengimplementasikan real-time UI update untuk like/unlike toggle, fungsi delete like dengan wp_delete_post(), dan versi jQuery-free menggunakan Axios.
1. Permission: Login Check + Nonce
Masalah Awal:
Meskipun kita sudah menambahkan is_user_logged_in() di PHP, REST API selalu mengembalikan false — karena tanpa nonce, WordPress tidak bisa mengidentifikasi siapa yang mengirim request.
Solusi: Nonce di JavaScript
// Di create like & delete like
$.ajax({
beforeSend: (xhr) => {
xhr.setRequestHeader('X-WP-Nonce', universityData.nonce);
},
// ... rest of ajax config
});PHP Side:
function createLike($data) {
if (is_user_logged_in()) {
// ... create post
} else {
die("Only logged in users can create a like.");
}
}Flow:
JavaScript mengirim nonce → REST API memverifikasi nonce
→ WordPress mengenali user → is_user_logged_in() = true
→ get_current_user_id() mengembalikan ID yang benar2. Restriction: 1 Like per User per Professor
Logika:
Sebelum membuat like baru, cek apakah user saat ini sudah pernah like professor yang diminta.
function createLike($data) {
if (is_user_logged_in()) {
$professor = sanitize_text_field($data['professorId']);
// Cek apakah like sudah ada
$existQuery = new WP_Query(array(
'author' => get_current_user_id(),
'post_type' => 'like',
'meta_query' => array(
array(
'key' => 'liked_professor_id',
'compare' => '=',
'value' => $professor
)
)
));
// Hanya buat like jika belum ada DAN ID benar-benar milik professor
if ($existQuery->found_posts == 0 AND get_post_type($professor) == 'professor') {
return wp_insert_post(array(
'post_type' => 'like',
'post_status' => 'publish',
'meta_input' => array(
'liked_professor_id' => $professor
)
));
} else {
die("Invalid professor id.");
}
} else {
die("Only logged in users can create a like.");
}
}Penjelasan Conditions:
| Check | Fungsi |
|---|---|
$existQuery->found_posts == 0 | User belum pernah like professor ini |
get_post_type($professor) == 'professor' | ID yang dikirim benar-benar milik post type professor |
Kasus yang Dicegah:
❌ User mengirim ID = 999 (tidak ada) → get_post_type() = false
❌ User mengirim ID = 50 (post type = 'post') → get_post_type() = 'post'
❌ User sudah like professor ini → found_posts > 0
✅ Belum like + ID valid professor → create like!3. Real-Time UI Update (Create Like)
Setelah berhasil create like, update UI tanpa refresh:
createLike(currentLikeBox) {
$.ajax({
beforeSend: (xhr) => {
xhr.setRequestHeader('X-WP-Nonce', universityData.nonce);
},
url: universityData.root_url + '/wp-json/university/v1/manageLike',
type: 'POST',
data: { professorId: currentLikeBox.data("professor") },
success: (response) => {
// 1. Heart berubah dari outline ke solid
currentLikeBox.attr("data-exists", "yes");
// 2. Increment angka like +1
var likeCount = parseInt(currentLikeBox.find(".like-count").html(), 10);
likeCount++;
currentLikeBox.find(".like-count").html(likeCount);
// 3. Simpan ID like post (untuk delete nanti)
currentLikeBox.attr("data-like", response);
console.log(response);
},
error: (response) => {
console.log(response);
}
});
}Penjelasan parseInt():
parseInt("3", 10) // → 3 (number)
// Argumen 1: string yang ingin dikonversi
// Argumen 2: base number system (10 = desimal, yang kita pakai sehari-hari)Increment/Decrement:
likeCount++ // Tambah 1 (untuk create)
likeCount-- // Kurang 1 (untuk delete)4. Delete Like: PHP Server-Side
File: inc/like-route.php
function deleteLike($data) {
$likeId = sanitize_text_field($data['like']);
// PERMISSION CHECK: hanya bisa hapus post milik sendiri + tipe 'like'
if (get_current_user_id() == get_post_field('post_author', $likeId)
AND get_post_type($likeId) == 'like') {
wp_delete_post($likeId, true); // true = skip trash, hapus permanen
return 'Congrats, like deleted.';
} else {
die("You do not have permission to delete that.");
}
}Penjelasan wp_delete_post():
| Argumen | Nilai | Penjelasan |
|---|---|---|
| 1. Post ID | $likeId | ID post yang akan dihapus |
| 2. Force delete | true | Skip trash, langsung hapus permanen |
Permission Checks:
// Check 1: User harus pemilik post
get_current_user_id() == get_post_field('post_author', $likeId)
// Check 2: Post harus bertipe 'like'
get_post_type($likeId) == 'like'KRITIS: Tanpa check ini, user bisa mengirim ID post APAPUN dan menghapusnya! Ini cara mencegah penghapusan sewenang-wenang.
get_post_field():
get_post_field('post_author', $likeId) // Ambil author ID dari post tertentu
// Argumen 1: nama field database (post_author, post_title, post_status, dll.)
// Argumen 2: ID post5. Real-Time UI Update (Delete Like)
deleteLike(currentLikeBox) {
$.ajax({
beforeSend: (xhr) => {
xhr.setRequestHeader('X-WP-Nonce', universityData.nonce);
},
url: universityData.root_url + '/wp-json/university/v1/manageLike',
type: 'DELETE',
data: { like: currentLikeBox.attr("data-like") },
success: (response) => {
// Kebalikan dari create:
currentLikeBox.attr("data-exists", "no"); // Heart outline
var likeCount = parseInt(currentLikeBox.find(".like-count").html(), 10);
likeCount--; // Decrement
currentLikeBox.find(".like-count").html(likeCount);
currentLikeBox.attr("data-like", ""); // Kosongkan ID
console.log(response);
},
error: (response) => {
console.log(response);
}
});
}6. Catatan: isset() untuk LikeBox HTML
Masalah:
Jika user belum like professor manapun, $existQuery->posts[0]->ID menyebabkan PHP warning karena array kosong.
Solusi:
<!-- SEBELUM (error jika belum like) -->
data-like="<?php echo $existQuery->posts[0]->ID; ?>"
<!-- SESUDAH (aman) -->
data-like="<?php if (isset($existQuery->posts[0]->ID)) echo $existQuery->posts[0]->ID; ?>"7. jQuery-Free LikeBox
Sama seperti fitur My Notes, versi tanpa jQuery tersedia sebagai drop-in replacement.
Perubahan Utama:
| jQuery | Vanilla JS / Axios |
|---|---|
$.ajax() | axios.post() / axios.delete() |
$(e.target).closest(".like-box") | e.target.closest(".like-box") |
.attr("data-exists", "yes") | .setAttribute("data-exists", "yes") |
.find(".like-count").html() | .querySelector(".like-count").innerHTML |
parseInt(...) | Sama (vanilla JS) |
Nonce Global Setup (Axios):
axios.defaults.headers.common['X-WP-Nonce'] = universityData.nonce;Set sekali di constructor, berlaku untuk semua request Axios — tidak perlu
beforeSendper-request.
Constructor dengan Conditional:
constructor() {
if (document.querySelector(".like-box")) {
// Hanya jalankan jika ada like box di halaman
axios.defaults.headers.common['X-WP-Nonce'] = universityData.nonce;
this.events();
}
}Diagram Arsitektur Like Feature
┌─────────────────────────────────────────────────┐
│ FRONT-END │
│ │
│ Like Box (.like-box) │
│ ├── data-exists="yes/no" │
│ ├── data-professor="104" │
│ ├── data-like="142" │
│ └── Klik → ourClickDispatcher() │
│ ├── data-exists="yes" → deleteLike() │
│ └── data-exists="no" → createLike() │
└─────────────────┬───────────────────────────┬────┘
│ POST │ DELETE
▼ ▼
┌─────────────────────────────────────────────────┐
│ /wp-json/university/v1/manageLike │
│ │
│ POST → createLike($data) │
│ ├── is_user_logged_in()? │
│ ├── existQuery→found_posts == 0? │
│ ├── get_post_type() == 'professor'? │
│ └── wp_insert_post() → return new post ID │
│ │
│ DELETE → deleteLike($data) │
│ ├── current_user == post_author? │
│ ├── get_post_type() == 'like'? │
│ └── wp_delete_post($id, true) │
└─────────────────────────────────────────────────┘