jfid – Welcome to an in-depth tutorial on how to enhance your WordPress site’s backend functionality using PHP.
In this guide, we’ll dive into several code snippets that can help streamline your WordPress admin experience, automate tasks, and manage resources more efficiently.
Overview
Managing a WordPress site can become cumbersome if the dashboard is cluttered or if the site’s resources are not well-managed. By using custom PHP code snippets, we can:
- Remove unnecessary dashboard widgets.
- Hide the WordPress logo and certain admin features.
- Add custom widgets to the dashboard.
- Manage images and attachments efficiently.
- Clean up unused tags and optimize tag management.
- Allow contributors to upload files.
- Redirect attachment pages to their parent posts.
- Remove duplicate images and unused images.
Let’s break down each section of the code and see how it enhances your WordPress site.
1. Remove Unnecessary Dashboard Widgets
Cleaning up the dashboard helps keep the admin area uncluttered and easier to navigate.
add_action('wp_dashboard_setup', 'remove_dashboard_widgets');
function remove_dashboard_widgets() {
$widgets_to_remove = [
'e-dashboard-overview',
'dashboard_site_health',
'dashboard_right_now',
'dashboard_activity',
'google_dashboard_widget',
'dashboard_quick_press',
'dashboard_primary'
];
foreach ($widgets_to_remove as $widget) {
remove_meta_box($widget, 'dashboard', in_array($widget, ['dashboard_quick_press', 'dashboard_primary']) ? 'side' : 'normal');
}
}
Explanation:
- Hooks into
wp_dashboard_setup
to customize the dashboard setup process. - Defines widgets to remove using
remove_meta_box
for a cleaner admin interface.
2. Hide Screen Options and Help Tab
These tabs can be hidden to streamline the interface for users who don’t need them.
add_action('admin_head', 'remove_screen_options_help');
function remove_screen_options_help() {
echo '<style>#screen-meta-links { display: none; }</style>';
}
Explanation:
- Uses
admin_head
to inject custom CSS that hides the screen options and help tab.
3. Remove WordPress Logo from Admin Bar
Removing the WordPress logo can provide a more white-labeled experience.
add_action('admin_bar_menu', 'remove_wp_logo', 999);
function remove_wp_logo($wp_admin_bar) {
$wp_admin_bar->remove_menu('wp-logo');
}
Explanation:
- Hooks into
admin_bar_menu
to remove the WordPress logo from the admin bar.
4. Enqueue Custom Scripts and Styles
To enhance the dashboard with custom scripts and styles, you can enqueue external resources.
function enqueue_dashboard_scripts() {
wp_enqueue_script('chartjs', 'https://cdn.jsdelivr.net/npm/chart.js', [], null, true);
wp_enqueue_script('flatpickr', 'https://cdn.jsdelivr.net/npm/flatpickr', [], null, true);
wp_enqueue_script('select2', 'https://cdn.jsdelivr.net/npm/select2@4.1.0-beta.1/dist/js/select2.min.js', [], null, true);
wp_enqueue_style('flatpickr-style', 'https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css', [], null, 'all');
wp_enqueue_style('select2-style', 'https://cdn.jsdelivr.net/npm/select2@4.1.0-beta.1/dist/css/select2.min.css', [], null, 'all');
}
add_action('admin_enqueue_scripts', 'enqueue_dashboard_scripts');
Explanation:
- Enqueues external JavaScript libraries like Chart.js, Flatpickr, and Select2 to enhance the admin dashboard functionalities with charts and date pickers.
5. Register and Display a Custom Dashboard Widget
Add a custom widget to the dashboard to display specific information, such as post statistics.
function register_dashboard_widgets() {
wp_add_dashboard_widget('custom_dashboard_widget', 'Grafik Perkembangan Post', 'custom_dashboard_widget_display');
}
add_action('wp_dashboard_setup', 'register_dashboard_widgets');
function custom_dashboard_widget_display() {
global $wpdb;
$authors = $wpdb->get_results(
"SELECT ID, display_name
FROM $wpdb->users
WHERE ID IN (
SELECT post_author
FROM $wpdb->posts
WHERE post_type = 'post' AND post_status = 'publish'
)",
OBJECT_K
);
?>
<div>
<input type="text" id="dateRangePicker" placeholder="Pilih rentang tanggal">
<select id="authorPicker" multiple>
<option value="all">Semua Pengguna</option>
<?php foreach ($authors as $author) : ?>
<option value="<?php echo esc_attr($author->ID); ?>"><?php echo esc_html($author->display_name); ?></option>
<?php endforeach; ?>
</select>
<div class="chart-container" style="position: relative; height:40vh; width:80vw">
<canvas id="postsChart"></canvas>
</div>
</div>
<script>
jQuery(document).ready(function($) {
// Inisialisasi Flatpickr
flatpickr('#dateRangePicker', {
mode: 'range',
dateFormat: 'Y-m-d',
onChange: function(selectedDates, dateStr, instance) {
updateChart(dateStr, $('#authorPicker').val());
}
});
// Ubah dropdown penulis menjadi Select2
$('#authorPicker').select2();
// Inisialisasi Chart.js dengan opsi responsif
var ctx = document.getElementById('postsChart').getContext('2d');
var postsChart = new Chart(ctx, {
type: 'line',
data: {
labels: [],
datasets: []
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true
}
}
}
});
// Fungsi untuk memperbarui grafik
function updateChart(dateRange, authorIds) {
var dates = dateRange.split(' to ');
var startDate = dates[0];
var endDate = dates[1];
$.post(ajaxurl, {
'action': 'get_posts_data',
'startDate': startDate,
'endDate': endDate,
'authorIds': authorIds
}, function(response) {
var data = JSON.parse(response);
postsChart.data.labels = data.labels;
postsChart.data.datasets = data.datasets;
postsChart.update();
});
}
// Perbarui grafik ketika penulis dipilih
$('#authorPicker').change(function() {
updateChart($('#dateRangePicker').val(), $(this).val());
});
});
</script>
<?php
}
Explanation:
- Registers a new dashboard widget using
wp_add_dashboard_widget
. - Displays a widget with a date range picker and author selection to show post statistics using Chart.js.
- Utilizes AJAX to fetch data dynamically and update the chart based on selected criteria.
6. Handle AJAX Requests for Custom Widget
Processes AJAX requests to fetch and display post data for the custom dashboard widget.
add_action('wp_ajax_get_posts_data', 'get_posts_data_callback');
function get_posts_data_callback() {
global $wpdb;
$startDate = sanitize_text_field($_POST['startDate']);
$endDate = sanitize_text_field($_POST['endDate']);
$authorIds = array_map('sanitize_text_field', $_POST['authorIds']);
$transient_key = 'posts_data_' . md5($startDate . '_' . $endDate . '_' . implode('_', $authorIds));
$data = get_transient($transient_key);
if ($data === false) {
$datasets = [];
$colors = [
'rgba(54, 162, 235, 1)',
'rgba(255, 99, 132, 1)',
'rgba(255, 206, 86, 1)',
'rgba(75, 192, 192, 1)',
'rgba(153, 102, 255, 1)',
'rgba(255, 159, 64, 1)'
];
$colorIndex = 0;
if (in_array('all', $authorIds)) {
$query = $wpdb->prepare(
"SELECT DATE(post_date) AS date, COUNT(*) AS count
FROM $wpdb->posts
WHERE post_type = 'post' AND post_status = 'publish'
AND post_date BETWEEN %s AND %s
GROUP BY date ORDER BY date ASC",
$startDate, $endDate
);
$results = $wpdb->get_results($query);
$labels = [];
$counts = [];
foreach ($results as $result) {
$labels[] = $result->date;
$counts[] = $result->count;
}
if (!empty($counts)) {
$data = [
'label' => 'Total Posts',
'data' => $counts,
'backgroundColor' => 'rgba(54, 162, 235, 0.2)',
'borderColor' => $colors[$colorIndex],
'borderWidth
' => 1
];
$datasets[] = $data;
}
} else {
foreach ($authorIds as $authorId) {
$query = $wpdb->prepare(
"SELECT DATE(post_date) AS date, COUNT(*) AS count
FROM $wpdb->posts
WHERE post_type = 'post' AND post_status = 'publish'
AND post_date BETWEEN %s AND %s
AND post_author = %d
GROUP BY date ORDER BY date ASC",
$startDate, $endDate, $authorId
);
$results = $wpdb->get_results($query);
$labels = [];
$counts = [];
foreach ($results as $result) {
$labels[] = $result->date;
$counts[] = $result->count;
}
if (!empty($counts)) {
$data = [
'label' => get_the_author_meta('display_name', $authorId),
'data' => $counts,
'backgroundColor' => 'rgba(54, 162, 235, 0.2)',
'borderColor' => $colors[$colorIndex],
'borderWidth' => 1
];
$datasets[] = $data;
$colorIndex = ($colorIndex + 1) % count($colors);
}
}
}
$data = ['labels' => $labels, 'datasets' => $datasets];
set_transient($transient_key, $data, HOUR_IN_SECONDS); // Cache query results for 1 hour
}
echo json_encode($data);
wp_die();
}
Explanation:
- Handles AJAX requests with the action
wp_ajax_get_posts_data
. - Fetches post data based on the selected date range and authors, using caching to improve performance.
- Returns data in JSON format to update the chart dynamically.
7. Delete Images When a Post is Permanently Deleted
Automate the cleanup of associated images when posts are deleted.
function delete_associated_images_on_trash_delete($post_id) {
// Ensure function only runs for posts permanently deleted
$post_status = get_post_status($post_id);
if ($post_status !== false && $post_status !== 'auto-draft' && $post_status !== 'draft' && $post_status !== 'trash') {
// Get all attachments associated with the post
$attachments = get_posts(array(
'post_type' => 'attachment',
'posts_per_page' => -1,
'post_status' => 'any',
'post_parent' => $post_id
));
foreach ($attachments as $attachment) {
$attachment_id = $attachment->ID;
// Check if the attachment is used by other posts
$is_used_elsewhere = check_if_attachment_used_elsewhere($attachment_id, $post_id);
if (!$is_used_elsewhere) {
// Delete the attachment if not used elsewhere
if (false === wp_delete_attachment($attachment_id, true)) {
error_log('Failed to delete attachment with ID: ' . $attachment_id);
}
}
}
}
}
function check_if_attachment_used_elsewhere($attachment_id, $post_id) {
global $wpdb;
// Check for the use of attachment as a featured image in other posts
$query = $wpdb->prepare(
"SELECT COUNT(*) FROM $wpdb->postmeta WHERE meta_key = '_thumbnail_id' AND meta_value = %d AND post_id != %d",
$attachment_id, $post_id
);
$count_thumbnail = $wpdb->get_var($query);
// Check for the use of attachment in the content of other posts
$query_content = $wpdb->prepare(
"SELECT COUNT(*) FROM $wpdb->posts WHERE post_content LIKE %s AND ID != %d",
'%' . $wpdb->esc_like($attachment_id) . '%', $post_id
);
$count_content = $wpdb->get_var($query_content);
return $count_thumbnail > 0 || $count_content > 0;
}
add_action('before_delete_post', 'delete_associated_images_on_trash_delete');
Explanation:
- Listens to the
before_delete_post
action to trigger the cleanup process. - Deletes images associated with a post if they are not used elsewhere, helping manage storage.
8. Remove Duplicate Images in Different Extensions
Efficiently manage your uploads folder by removing duplicate image files.
function delete_jpg_if_webp_exists() {
$upload_dir = wp_upload_dir();
$base_dir = $upload_dir['basedir'];
$webp_files = [];
// Efficiently gather .webp files using a generator
$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($base_dir));
foreach ($files as $file) {
if ($file->isFile() && $file->getExtension() === 'webp') {
$webp_files[$file->getBasename('.webp')] = true;
}
}
// Reset iterator to delete .jpg and .jpeg files that have .webp duplicates
foreach ($files as $file) {
if ($file->isFile()) {
$extension = $file->getExtension();
if ($extension === 'jpg' || $extension === 'jpeg') {
$basename = $file->getBasename('.' . $extension);
if (isset($webp_files[$basename])) {
unlink($file->getRealPath());
echo "Deleted: " . $file->getRealPath() . "<br>";
}
}
}
}
}
// Run the function in the WordPress admin area
add_action('admin_init', 'delete_jpg_if_webp_exists');
Explanation:
- Deletes JPEG images if a corresponding WebP version exists, freeing up disk space.
- Utilizes PHP’s
RecursiveIteratorIterator
andRecursiveDirectoryIterator
for efficient file handling.
9. Redirect Attachment Pages to Parent Post
Improve user experience by redirecting attachment pages to their parent posts.
function redirect_attachment_to_parent_post() {
if (is_attachment()) {
global $post;
// Check if the attachment has a parent post
if (isset($post->post_parent) && $post->post_parent != 0) {
wp_redirect(get_permalink($post->post_parent), 301);
exit;
} else {
wp_redirect(home_url(), 301);
exit;
}
}
}
add_action('template_redirect', 'redirect_attachment_to_parent_post');
Explanation:
- Redirects visitors from attachment pages to the parent post or homepage, improving site navigation and SEO.
10. Allow Contributors to Upload Files
Enhance the role of contributors by allowing them to upload files.
function allow_contributor_uploads() {
// Get the contributor role
$contributor = get_role('contributor');
// Ensure the contributor role exists before adding capabilities
if ($contributor) {
$contributor->add_cap('upload_files');
} else {
error_log('Role "contributor" not found.');
}
}
// Hook to 'admin_init' to add upload capabilities to contributors
add_action('admin_init', 'allow_contributor_uploads');
Explanation:
- Grants the capability to upload files to users with the contributor role, allowing them more flexibility in content creation.
11. Clean Up Unused Tags and Manage Spam
Optimize your site’s tags by cleaning up unused ones and managing spam.
function get_cleanup_batch_status() {
$status = get_transient('cleanup_batch_status');
return $status ?: array(
'offset' => 0,
'finished' => false
);
}
function update_cleanup_batch_status($status) {
set_transient('cleanup_batch_status', $status, HOUR_IN_SECONDS);
}
function reset_cleanup_batch_status() {
delete_transient('cleanup_batch_status');
}
function organize_tags() {
if (!current_user_can('manage_options')) {
return;
}
$similarity_threshold = 3;
$all_tags = get_terms(array(
'taxonomy' => 'post_tag',
'hide_empty' => false,
'orderby' => 'name',
'order' => 'ASC',
'number' => 0, // Retrieve all tags
));
if (empty($all_tags)) {
return;
}
$seen_tags = array();
$tags_to_delete = array();
$tag_mappings = array();
foreach ($all_tags as $tag) {
$lowercase_tag_name = strtolower($tag->name);
foreach ($seen_tags as $seen_tag_name => $seen_tag_id) {
if (levenshtein($lowercase_tag_name, $seen_tag_name) <= $similarity_threshold) {
$tag_mappings[$tag->term_id] = $seen_tag_id;
$tags_to_delete[] = $tag->term_id;
continue 2;
}
}
$seen_tags[$lowercase_tag_name] = $tag->term_id;
if (!preg_match('/^[a-zA-Z0-9\s]+$/', $tag->name) || str_word_count($tag->name) > 3) {
$tags_to_delete[] = $tag->term_id;
}
}
foreach ($tag_mappings as $old_tag_id => $new_tag_id) {
$posts = get_posts(array(
'post_type' => 'any',
'numberposts' => -1,
'tax_query
' => array(
array(
'taxonomy' => 'post_tag',
'field' => 'term_id',
'terms' => $old_tag_id,
),
),
));
foreach ($posts as $post) {
wp_set_post_terms($post->ID, array($new_tag_id), 'post_tag', true);
}
wp_delete_term($old_tag_id, 'post_tag');
}
foreach ($tags_to_delete as $tag_id) {
wp_delete_term($tag_id, 'post_tag');
}
echo '<div class="updated"><p>Tag organization complete.</p></div>';
}
function cleanup_unused_tags() {
if (!current_user_can('manage_options')) {
return;
}
// Get all tags
$all_tags = get_terms(array(
'taxonomy' => 'post_tag',
'hide_empty' => false,
));
if (empty($all_tags)) {
return;
}
$tags_to_delete = array();
foreach ($all_tags as $tag) {
if ($tag->count == 0) {
// Add unused tags to the deletion list
$tags_to_delete[] = $tag->term_id;
}
}
foreach ($tags_to_delete as $tag_id) {
wp_delete_term($tag_id, 'post_tag');
}
echo '<div class="updated"><p>Unused tags cleanup complete. ' . count($tags_to_delete) . ' tags deleted.</p></div>';
}
function cleanup_tags_admin_menu() {
add_submenu_page('tools.php', 'Cleanup Tags', 'Cleanup Tags', 'manage_options', 'cleanup-tags', 'cleanup_tags_page');
}
add_action('admin_menu', 'cleanup_tags_admin_menu');
function cleanup_tags_page() {
echo '<div class="wrap">';
echo '<h1>Cleanup Tags</h1>';
if (isset($_POST['organize_tags'])) {
organize_tags();
}
if (isset($_POST['cleanup_unused_tags'])) {
cleanup_unused_tags();
}
echo '<form method="post">';
echo '<p><input type="submit" name="organize_tags" class="button button-primary" value="Organize Tags"></p>';
echo '<p><input type="submit" name="cleanup_unused_tags" class="button button-primary" value="Cleanup Unused Tags"></p>';
echo '</form>';
echo '</div>';
}
Explanation:
- Provides an admin interface for organizing and cleaning up tags.
- Uses the Levenshtein distance algorithm to merge similar tags and deletes unused ones to maintain a tidy tag taxonomy.
12. Prevent Tag Spam
Automate the management of tags to prevent spam and clutter.
function manage_post_tags($post_id) {
// Stop execution if this is a post revision
if (wp_is_post_revision($post_id)) {
return;
}
// Set the maximum number of words per tag
$max_words_per_tag = 3;
// Set the maximum number of tags per post
$max_tags_per_post = 5;
// Get current tags of the post
$tags = wp_get_post_tags($post_id, ['fields' => 'all']);
if (empty($tags)) {
return;
}
$valid_tags = [];
$existing_tags = [];
foreach ($tags as $tag) {
// Sanitize tag name
$sanitized_tag_name = sanitize_text_field($tag->name);
// Check the number of words in the tag and its uniqueness
if (str_word_count($sanitized_tag_name) <= $max_words_per_tag && !isset($existing_tags[$sanitized_tag_name])) {
$existing_tags[$sanitized_tag_name] = $tag->term_id;
$valid_tags[] = $tag->term_id;
// Stop if we have reached the maximum number of tags
if (count($valid_tags) >= $max_tags_per_post) {
break;
}
}
}
// Remove tags that exceed the word count, total number limits, or are duplicates
$tags_to_remove = array_diff(array_column($tags, 'term_id'), $valid_tags);
if (!empty($tags_to_remove)) {
wp_remove_object_terms($post_id, $tags_to_remove, 'post_tag');
}
}
add_action('save_post', 'manage_post_tags');
Explanation:
- Limits the number of tags and the number of words per tag to prevent tag spam.
- Automatically sanitizes and cleans up tags during the post-save process.
13. Delete Unused Attached Images
Automate the cleanup of unattached images to save space.
function update_unused_attachments_status() {
global $wpdb;
// Use transient to store query results temporarily
if (false === ($attachments = get_transient('unused_attachments'))) {
// Query to get attachments not linked to any post
$query = "
SELECT p.ID
FROM {$wpdb->posts} p
LEFT JOIN {$wpdb->postmeta} pm ON pm.meta_value = p.ID AND pm.meta_key = '_thumbnail_id'
LEFT JOIN {$wpdb->posts} pp ON pp.post_content LIKE CONCAT('%', p.guid, '%')
WHERE p.post_type = 'attachment'
AND p.post_parent = 0
AND pm.post_id IS NULL
AND pp.ID IS NULL
";
$attachments = $wpdb->get_results($query, OBJECT);
// Store query results in a transient for 12 hours
set_transient('unused_attachments', $attachments, 12 * HOUR_IN_SECONDS);
}
if (!empty($attachments)) {
// Process attachments in batches for efficiency
foreach ($attachments as $attachment) {
$attachment_id = $attachment->ID;
// Update attachment status to 'unattached' if not linked to any post
$updated = $wpdb->update(
$wpdb->posts,
array('post_parent' => 0), // Set post_parent to 0
array('ID' => $attachment_id)
);
if ($updated === false) {
error_log('Failed to update attachment status with ID: ' . $attachment_id);
}
}
}
}
// Run function on the 'init' hook
add_action('init', 'update_unused_attachments_status');
// Schedule a cron job to update unused attachment status
if (!wp_next_scheduled('update_unused_attachments_status_cron')) {
wp_schedule_event(time(), 'daily', 'update_unused_attachments_status_cron');
}
add_action('update_unused_attachments_status_cron', 'update_unused_attachments_status');
Explanation:
- Identifies unattached images and updates their status, freeing up server resources.
- Uses WordPress transients to cache query results and improve performance.
By incorporating these code snippets, you can significantly enhance your WordPress site’s functionality and efficiency.
Customize the dashboard, manage resources more effectively, and provide a better experience for your users and content creators.