feat(ui): add tab navigation, history tab with generated content list, single item delete, and update domain URLs

This commit is contained in:
idtemankampus-creator
2026-09-18 08:34:04 +07:00
parent e8b955a650
commit 6159c8fd15
+152 -14
View File
@@ -1,12 +1,12 @@
<?php <?php
/** /**
* Plugin Name: DummyLab Dummy Post & Content Generator * Plugin Name: DummyLab Dummy Post & Content Generator
* Plugin URI: https://apps.masbudikusuma.my.id/dummylab-wp * Plugin URI: https://apps.masbudi.my.id/dummylab-wp
* Description: Advanced dummy content generator for WordPress development with safety controls and bulk purge features. * Description: Advanced dummy content generator for WordPress development with safety controls and bulk purge features.
* Version: 1.3.0 * Version: 1.4.0
* Author: masbudikusuma * Author: masbudikusuma
* Author URI: https://masbudi.my.id * Author URI: https://masbudi.my.id
* Donate link: https://saweria.co/masbudikusuma * Donate link: https://donate.masbudi.my.id/dummylab-wp
* Text Domain: dummylab * Text Domain: dummylab
* License: GPL-2.0+ * License: GPL-2.0+
* License URI: http://www.gnu.org/licenses/gpl-2.0.txt * License URI: http://www.gnu.org/licenses/gpl-2.0.txt
@@ -32,8 +32,8 @@ class DummyLab {
add_action('admin_init', [$this, 'handle_activation_redirect']); add_action('admin_init', [$this, 'handle_activation_redirect']);
add_action('admin_post_dummylab_generate', [$this, 'handle_generate']); add_action('admin_post_dummylab_generate', [$this, 'handle_generate']);
add_action('admin_post_dummylab_purge', [$this, 'handle_purge']); add_action('admin_post_dummylab_purge', [$this, 'handle_purge']);
add_action('admin_post_dummylab_delete_single', [$this, 'handle_delete_single']);
// Filter untuk deskripsi dinamis (ID/EN) dan Link Donasi di halaman Plugin Admin
add_filter('all_plugins', [$this, 'dynamic_plugin_description']); add_filter('all_plugins', [$this, 'dynamic_plugin_description']);
add_filter('plugin_row_meta', [$this, 'add_plugin_row_meta'], 10, 2); add_filter('plugin_row_meta', [$this, 'add_plugin_row_meta'], 10, 2);
} }
@@ -52,7 +52,6 @@ class DummyLab {
} }
} }
// Mengubah deskripsi plugin di wp-admin/plugins.php sesuai bahasa situs (ID / EN)
public function dynamic_plugin_description($all_plugins) { public function dynamic_plugin_description($all_plugins) {
$plugin_file = plugin_basename(__FILE__); $plugin_file = plugin_basename(__FILE__);
if (isset($all_plugins[$plugin_file])) { if (isset($all_plugins[$plugin_file])) {
@@ -66,12 +65,11 @@ class DummyLab {
return $all_plugins; return $all_plugins;
} }
// Menambahkan Link Donasi di sebelah "Visit plugin site"
public function add_plugin_row_meta($links, $file) { public function add_plugin_row_meta($links, $file) {
if ($file === plugin_basename(__FILE__)) { if ($file === plugin_basename(__FILE__)) {
$locale = get_locale(); $locale = get_locale();
$donate_label = (strpos($locale, 'id') === 0) ? '☕ Donasi' : '☕ Donate'; $donate_label = (strpos($locale, 'id') === 0) ? '☕ Donasi' : '☕ Donate';
$links[] = '<a href="https://saweria.co/masbudikusuma" target="_blank" style="color: #b35900; font-weight: 600;">' . $donate_label . '</a>'; $links[] = '<a href="https://donate.masbudi.my.id/dummylab-wp" target="_blank" style="color: #b35900; font-weight: 600;">' . $donate_label . '</a>';
} }
return $links; return $links;
} }
@@ -92,6 +90,7 @@ class DummyLab {
} }
$ui_lang = isset($_GET['ui_lang']) && $_GET['ui_lang'] === 'en' ? 'en' : 'id'; $ui_lang = isset($_GET['ui_lang']) && $_GET['ui_lang'] === 'en' ? 'en' : 'id';
$current_tab = isset($_GET['tab']) && $_GET['tab'] === 'history' ? 'history' : 'generator';
$users = get_users(['fields' => ['ID', 'display_name']]); $users = get_users(['fields' => ['ID', 'display_name']]);
$query = new WP_Query([ $query = new WP_Query([
@@ -108,7 +107,7 @@ class DummyLab {
?> ?>
<style> <style>
.dummylab-wrap { max-width: 1200px; margin: 20px 20px 0 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; } .dummylab-wrap { max-width: 1200px; margin: 20px 20px 0 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; }
.dummylab-header { background: #fff; border: 1px solid #c3c4c7; border-radius: 8px; padding: 20px 24px; display: flex; justify-content: space-between; align-items: center; box-shadow: 0 1px 3px rgba(0,0,0,0.04); margin-bottom: 24px; } .dummylab-header { background: #fff; border: 1px solid #c3c4c7; border-radius: 8px; padding: 20px 24px; display: flex; justify-content: space-between; align-items: center; box-shadow: 0 1px 3px rgba(0,0,0,0.04); margin-bottom: 20px; }
.dummylab-header h1 { margin: 0 0 6px 0; font-size: 22px; font-weight: 600; color: #1d2327; display: flex; align-items: center; gap: 8px; } .dummylab-header h1 { margin: 0 0 6px 0; font-size: 22px; font-weight: 600; color: #1d2327; display: flex; align-items: center; gap: 8px; }
.dummylab-header p { margin: 0; color: #646970; font-size: 13px; } .dummylab-header p { margin: 0; color: #646970; font-size: 13px; }
@@ -116,6 +115,12 @@ class DummyLab {
.dummylab-lang-btn { text-decoration: none; padding: 5px 14px; border-radius: 16px; font-size: 12px; font-weight: 600; color: #50575e; transition: all 0.2s ease; } .dummylab-lang-btn { text-decoration: none; padding: 5px 14px; border-radius: 16px; font-size: 12px; font-weight: 600; color: #50575e; transition: all 0.2s ease; }
.dummylab-lang-btn.active { background: #2271b1; color: #fff; box-shadow: 0 1px 2px rgba(0,0,0,0.1); } .dummylab-lang-btn.active { background: #2271b1; color: #fff; box-shadow: 0 1px 2px rgba(0,0,0,0.1); }
/* Modern Tabs */
.dummylab-tabs { display: flex; gap: 8px; margin-bottom: 20px; border-bottom: 2px solid #e2e8f0; }
.dummylab-tab-item { padding: 10px 18px; text-decoration: none; font-weight: 600; color: #646970; border-bottom: 2px solid transparent; margin-bottom: -2px; font-size: 14px; transition: all 0.2s ease; display: flex; align-items: center; gap: 8px; }
.dummylab-tab-item:hover { color: #2271b1; }
.dummylab-tab-item.active { color: #2271b1; border-bottom-color: #2271b1; }
.dummylab-container { display: flex; gap: 24px; align-items: flex-start; } .dummylab-container { display: flex; gap: 24px; align-items: flex-start; }
.dummylab-main-card { flex: 2; background: #fff; border: 1px solid #c3c4c7; border-radius: 8px; padding: 24px; box-shadow: 0 1px 3px rgba(0,0,0,0.04); min-width: 550px; } .dummylab-main-card { flex: 2; background: #fff; border: 1px solid #c3c4c7; border-radius: 8px; padding: 24px; box-shadow: 0 1px 3px rgba(0,0,0,0.04); min-width: 550px; }
.dummylab-sidebar { flex: 1; display: flex; flex-direction: column; gap: 20px; min-width: 320px; } .dummylab-sidebar { flex: 1; display: flex; flex-direction: column; gap: 20px; min-width: 320px; }
@@ -164,17 +169,28 @@ class DummyLab {
</div> </div>
<div class="dummylab-wrap"> <div class="dummylab-wrap">
<!-- Header Banner -->
<div class="dummylab-header"> <div class="dummylab-header">
<div> <div>
<h1>🧪 DummyLab <span style="font-size: 14px; font-weight: 400; color: #646970;">v1.3.0</span></h1> <h1>🧪 DummyLab <span style="font-size: 14px; font-weight: 400; color: #646970;">v1.4.0</span></h1>
<p><?php echo $t['created_by']; ?> <a href="https://masbudi.my.id" target="_blank" style="text-decoration: none; font-weight: 600; color: #2271b1;">masbudikusuma</a> &bull; <a href="https://apps.masbudikusuma.my.id/dummylab-wp" target="_blank" style="text-decoration: none; color: #2271b1;"><?php echo $t['plugin_doc']; ?></a> &bull; <a href="https://saweria.co/masbudikusuma" target="_blank" style="text-decoration: none; font-weight: 600; color: #b35900;">☕ <?php echo $t['link_donate']; ?></a></p> <p><?php echo $t['created_by']; ?> <a href="https://masbudi.my.id" target="_blank" style="text-decoration: none; font-weight: 600; color: #2271b1;">masbudikusuma</a> &bull; <a href="https://apps.masbudi.my.id/dummylab-wp" target="_blank" style="text-decoration: none; color: #2271b1;"><?php echo $t['plugin_doc']; ?></a> &bull; <a href="https://donate.masbudi.my.id/dummylab-wp" target="_blank" style="text-decoration: none; font-weight: 600; color: #b35900;">☕ <?php echo $t['link_donate']; ?></a></p>
</div> </div>
<div class="dummylab-lang-switch"> <div class="dummylab-lang-switch">
<a href="<?php echo esc_url(add_query_arg('ui_lang', 'id')); ?>" class="dummylab-lang-btn <?php echo $ui_lang === 'id' ? 'active' : ''; ?>">🇮🇩 ID</a> <a href="<?php echo esc_url(add_query_arg(['ui_lang' => 'id', 'tab' => $current_tab])); ?>" class="dummylab-lang-btn <?php echo $ui_lang === 'id' ? 'active' : ''; ?>">🇮🇩 ID</a>
<a href="<?php echo esc_url(add_query_arg('ui_lang', 'en')); ?>" class="dummylab-lang-btn <?php echo $ui_lang === 'en' ? 'active' : ''; ?>">🇬🇧 EN</a> <a href="<?php echo esc_url(add_query_arg(['ui_lang' => 'en', 'tab' => $current_tab])); ?>" class="dummylab-lang-btn <?php echo $ui_lang === 'en' ? 'active' : ''; ?>">🇬🇧 EN</a>
</div> </div>
</div> </div>
<!-- Navigation Tabs -->
<div class="dummylab-tabs">
<a href="<?php echo esc_url(add_query_arg(['tab' => 'generator', 'ui_lang' => $ui_lang])); ?>" class="dummylab-tab-item <?php echo $current_tab === 'generator' ? 'active' : ''; ?>">
🚀 <?php echo $t['tab_generator']; ?>
</a>
<a href="<?php echo esc_url(add_query_arg(['tab' => 'history', 'ui_lang' => $ui_lang])); ?>" class="dummylab-tab-item <?php echo $current_tab === 'history' ? 'active' : ''; ?>">
📜 <?php echo $t['tab_history']; ?> (<?php echo esc_html($dummy_count); ?>)
</a>
</div>
<?php if (isset($_GET['status'])): ?> <?php if (isset($_GET['status'])): ?>
<div class="notice notice-success is-dismissible" style="border-radius: 6px; margin-bottom: 20px; padding: 12px 16px;"> <div class="notice notice-success is-dismissible" style="border-radius: 6px; margin-bottom: 20px; padding: 12px 16px;">
<p style="font-size: 14px; margin: 0; display: flex; align-items: center; gap: 12px; flex-wrap: wrap;"> <p style="font-size: 14px; margin: 0; display: flex; align-items: center; gap: 12px; flex-wrap: wrap;">
@@ -188,12 +204,16 @@ class DummyLab {
echo '<a href="' . esc_url($target_url) . '" class="button button-secondary" style="font-weight: 600; text-decoration: none;">' . esc_html($t['btn_view_list']) . ' ' . esc_html($type_label) . ' &rarr;</a>'; echo '<a href="' . esc_url($target_url) . '" class="button button-secondary" style="font-weight: 600; text-decoration: none;">' . esc_html($t['btn_view_list']) . ' ' . esc_html($type_label) . ' &rarr;</a>';
} elseif ($_GET['status'] === 'purged') { } elseif ($_GET['status'] === 'purged') {
echo '<span>🧹 <strong>' . esc_html(intval($_GET['count'])) . '</strong> ' . esc_html($t['msg_purged']) . '</span>'; echo '<span>🧹 <strong>' . esc_html(intval($_GET['count'])) . '</strong> ' . esc_html($t['msg_purged']) . '</span>';
} elseif ($_GET['status'] === 'deleted_single') {
echo '<span>🗑️ ' . esc_html($t['msg_deleted_single']) . '</span>';
} }
?> ?>
</p> </p>
</div> </div>
<?php endif; ?> <?php endif; ?>
<?php if ($current_tab === 'generator'): ?>
<!-- GENERATOR TAB CONTENT -->
<div class="dummylab-container"> <div class="dummylab-container">
<div class="dummylab-main-card"> <div class="dummylab-main-card">
<form id="dummylab-generate-form" method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>"> <form id="dummylab-generate-form" method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>">
@@ -331,6 +351,84 @@ class DummyLab {
</div> </div>
</div> </div>
</div> </div>
<?php else: ?>
<!-- HISTORY TAB CONTENT -->
<div class="dummylab-main-card" style="min-width: 100%;">
<h2>📜 <?php echo $t['history_title']; ?></h2>
<p style="color: #646970; margin-bottom: 20px;"><?php echo $t['history_desc']; ?></p>
<?php
$history_query = new WP_Query([
'post_type' => ['post', 'page'],
'posts_per_page' => 50,
'meta_key' => $this->meta_key,
'meta_value' => '1',
'post_status' => 'any',
]);
?>
<?php if ($history_query->have_posts()): ?>
<table class="wp-list-table widefat fixed striped table-view-list" style="border-radius: 6px; overflow: hidden; border: 1px solid #c3c4c7;">
<thead>
<tr>
<th style="font-weight: 600; width: 35%;"><?php echo $t['th_title']; ?></th>
<th style="font-weight: 600; width: 12%;"><?php echo $t['th_type']; ?></th>
<th style="font-weight: 600; width: 15%;"><?php echo $t['th_status']; ?></th>
<th style="font-weight: 600; width: 15%;"><?php echo $t['th_author']; ?></th>
<th style="font-weight: 600; width: 13%;"><?php echo $t['th_date']; ?></th>
<th style="font-weight: 600; width: 10%; text-align: right;"><?php echo $t['th_action']; ?></th>
</tr>
</thead>
<tbody>
<?php while ($history_query->have_posts()): $history_query->the_post();
$item_id = get_the_ID();
$item_type = get_post_type($item_id);
$edit_link = get_edit_post_link($item_id);
$view_link = get_permalink($item_id);
?>
<tr>
<td>
<strong><a href="<?php echo esc_url($edit_link); ?>" target="_blank"><?php the_title(); ?></a></strong>
<?php if (has_post_thumbnail($item_id)): ?>
<span style="font-size: 11px; background: #e0f2fe; color: #0369a1; padding: 2px 6px; border-radius: 4px; margin-left: 6px;">🖼️ Image</span>
<?php endif; ?>
</td>
<td>
<span style="text-transform: capitalize; font-weight: 600; color: #475569;">
<?php echo esc_html($item_type); ?>
</span>
</td>
<td>
<span style="display: inline-block; padding: 2px 8px; border-radius: 12px; font-size: 11px; font-weight: 600; background: #f1f5f9; color: #334155;">
<?php echo esc_html(get_post_status($item_id)); ?>
</span>
</td>
<td><?php the_author(); ?></td>
<td><?php echo get_the_date('d M Y H:i'); ?></td>
<td style="text-align: right;">
<div style="display: flex; gap: 8px; justify-content: flex-end; align-items: center;">
<a href="<?php echo esc_url($view_link); ?>" target="_blank" title="View" style="text-decoration: none;">👁️</a>
<a href="<?php echo esc_url($edit_link); ?>" target="_blank" title="Edit" style="text-decoration: none;">✏️</a>
<form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>" style="display: inline;" onsubmit="return confirm('Hapus item dummy ini?');">
<?php wp_nonce_field('dummylab_delete_single_action', 'dummylab_nonce'); ?>
<input type="hidden" name="action" value="dummylab_delete_single">
<input type="hidden" name="post_id" value="<?php echo esc_attr($item_id); ?>">
<input type="hidden" name="ui_lang" value="<?php echo esc_attr($ui_lang); ?>">
<button type="submit" style="background: none; border: none; cursor: pointer; padding: 0;" title="Delete">🗑️</button>
</form>
</div>
</td>
</tr>
<?php endwhile; wp_reset_postdata(); ?>
</tbody>
</table>
<?php else: ?>
<div style="padding: 40px; text-align: center; background: #f8fafc; border-radius: 6px; border: 1px dashed #cbd5e1;">
<p style="font-size: 16px; color: #646970; margin: 0;"><?php echo $t['empty_history']; ?></p>
</div>
<?php endif; ?>
</div>
<?php endif; ?>
</div> </div>
<script> <script>
@@ -360,10 +458,13 @@ class DummyLab {
'created_by' => 'Created by', 'created_by' => 'Created by',
'plugin_doc' => 'Plugin Documentation', 'plugin_doc' => 'Plugin Documentation',
'link_donate' => 'Donate Support', 'link_donate' => 'Donate Support',
'tab_generator' => 'Generator',
'tab_history' => 'Generated History',
'msg_generated' => 'dummy item(s) generated successfully!', 'msg_generated' => 'dummy item(s) generated successfully!',
'check_msg' => 'Please check your %s section.', 'check_msg' => 'Please check your %s section.',
'btn_view_list' => 'View List of', 'btn_view_list' => 'View List of',
'msg_purged' => 'dummy items deleted successfully!', 'msg_purged' => 'dummy items deleted successfully!',
'msg_deleted_single' => 'Dummy item deleted successfully.',
'loading_title' => 'Generating dummy content...', 'loading_title' => 'Generating dummy content...',
'loading_subtitle' => 'Please wait a few moments while we create your test data and images.', 'loading_subtitle' => 'Please wait a few moments while we create your test data and images.',
'sec_general' => 'General Settings', 'sec_general' => 'General Settings',
@@ -414,6 +515,15 @@ class DummyLab {
'safety_item1' => 'All dummy posts tracked via unique metadata key.', 'safety_item1' => 'All dummy posts tracked via unique metadata key.',
'safety_item2' => 'Clean purge removes featured images from Media Library.', 'safety_item2' => 'Clean purge removes featured images from Media Library.',
'safety_item3' => 'Safe for staging and local theme testing.', 'safety_item3' => 'Safe for staging and local theme testing.',
'history_title' => 'Generated Dummy Content List',
'history_desc' => 'List of all posts and pages currently generated by DummyLab.',
'th_title' => 'Title',
'th_type' => 'Type',
'th_status' => 'Status',
'th_author' => 'Author',
'th_date' => 'Date',
'th_action' => 'Actions',
'empty_history' => 'No dummy content generated yet.',
]; ];
} }
@@ -421,10 +531,13 @@ class DummyLab {
'created_by' => 'Dibuat oleh', 'created_by' => 'Dibuat oleh',
'plugin_doc' => 'Dokumentasi Plugin', 'plugin_doc' => 'Dokumentasi Plugin',
'link_donate' => 'Dukung Donasi', 'link_donate' => 'Dukung Donasi',
'tab_generator' => 'Generator',
'tab_history' => 'Riwayat Konten',
'msg_generated' => 'konten dummy berhasil dibuat!', 'msg_generated' => 'konten dummy berhasil dibuat!',
'check_msg' => 'Silakan cek daftar %s Anda.', 'check_msg' => 'Silakan cek daftar %s Anda.',
'btn_view_list' => 'Lihat Daftar', 'btn_view_list' => 'Lihat Daftar',
'msg_purged' => 'data dummy berhasil dibersihkan!', 'msg_purged' => 'data dummy berhasil dibersihkan!',
'msg_deleted_single' => 'Item dummy berhasil dihapus.',
'loading_title' => 'Sedang membuat konten dummy...', 'loading_title' => 'Sedang membuat konten dummy...',
'loading_subtitle' => 'Mohon tunggu beberapa saat. Server sedang mengunduh gambar dan membuat data artikel.', 'loading_subtitle' => 'Mohon tunggu beberapa saat. Server sedang mengunduh gambar dan membuat data artikel.',
'sec_general' => 'Pengaturan Utama', 'sec_general' => 'Pengaturan Utama',
@@ -475,6 +588,15 @@ class DummyLab {
'safety_item1' => 'Setiap postingan dummy dilindungi metadata internal unik.', 'safety_item1' => 'Setiap postingan dummy dilindungi metadata internal unik.',
'safety_item2' => 'Fitur Purge juga membersihkan berkas gambar dari Media Library.', 'safety_item2' => 'Fitur Purge juga membersihkan berkas gambar dari Media Library.',
'safety_item3' => 'Aman digunakan pada lingkungan staging maupun lokal.', 'safety_item3' => 'Aman digunakan pada lingkungan staging maupun lokal.',
'history_title' => 'Daftar Konten Dummy Dihasilkan',
'history_desc' => 'Daftar seluruh artikel dan halaman dummy yang aktif dibuat oleh DummyLab.',
'th_title' => 'Judul',
'th_type' => 'Tipe',
'th_status' => 'Status',
'th_author' => 'Penulis',
'th_date' => 'Tanggal',
'th_action' => 'Aksi',
'empty_history' => 'Belum ada konten dummy yang dibuat.',
]; ];
} }
@@ -595,7 +717,23 @@ class DummyLab {
} }
} }
wp_redirect(admin_url('tools.php?page=dummylab&ui_lang=' . $ui_lang . '&status=generated&count=' . $count . '&type=' . $post_type)); wp_redirect(admin_url('tools.php?page=dummylab&tab=generator&ui_lang=' . $ui_lang . '&status=generated&count=' . $count . '&type=' . $post_type));
exit;
}
public function handle_delete_single() {
if (!current_user_can('manage_options') || !check_admin_referer('dummylab_delete_single_action', 'dummylab_nonce')) {
wp_die('Akses ditolak.');
}
$post_id = isset($_POST['post_id']) ? intval($_POST['post_id']) : 0;
$ui_lang = isset($_POST['ui_lang']) ? sanitize_text_field($_POST['ui_lang']) : 'id';
if ($post_id > 0) {
wp_delete_post($post_id, true);
}
wp_redirect(admin_url('tools.php?page=dummylab&tab=history&ui_lang=' . $ui_lang . '&status=deleted_single'));
exit; exit;
} }
@@ -624,7 +762,7 @@ class DummyLab {
} }
} }
wp_redirect(admin_url('tools.php?page=dummylab&ui_lang=' . $ui_lang . '&status=purged&count=' . $purged_count)); wp_redirect(admin_url('tools.php?page=dummylab&tab=generator&ui_lang=' . $ui_lang . '&status=purged&count=' . $purged_count));
exit; exit;
} }