WordPress Admin Page Creation
February 20, 2025 Β· AI Conversations
FROM wp_6hp6nqm250_postmeta
WHERE meta_key = '_customer_user'
AND meta_value > 0
GROUP BY meta_value
ORDER BY order_count DESC LIMIT 0,25;
Create wordpress admin page for above with pagination
—
### Steps to Implement:
1. **Create the Admin Menu Page**
2. **Query the Database with Pagination**
3. **Display Results in a Table with Pagination Controls**
—
### 1. Register the Admin Page
Add the following code to your theme's `functions.php` or a custom plugin.
function custom_orders_admin_menu() {
add_menu_page(
'Top Customers by Orders', // Page title
'Top Customers', // Menu title
'manage_options', // Capability
'top-customers-orders', // Menu slug
'render_top_customers_page', // Callback function
'dashicons-chart-bar', // Icon
25 // Position
);
}
add_action('admin_menu', 'custom_orders_admin_menu');
—
### 2. Create the Admin Page Callback Function
This function fetches data from `wp_postmeta` with pagination and displays it in an HTML table.
function render_top_customers_page() {
global $wpdb;
$table_name = $wpdb->prefix . 'postmeta';
$meta_key = '_customer_user';
$per_page = 10; // Number of records per page
$current_page = isset($_GET['paged']) ? max(1, intval($_GET['paged'])) : 1;
$offset = ($current_page - 1) * $per_page;
// Count total records for pagination
$total_records = $wpdb->get_var("
SELECT COUNT(DISTINCT meta_value) FROM $table_name WHERE meta_key = '$meta_key' AND meta_value > 0
");
$total_pages = ceil($total_records / $per_page);
// Query data with pagination
$results = $wpdb->get_results($wpdb->prepare("
SELECT meta_value as user_id, COUNT(post_id) as order_count
FROM $table_name
WHERE meta_key = %s AND meta_value > 0
GROUP BY meta_value
ORDER BY order_count DESC
LIMIT %d, %d
", $meta_key, $offset, $per_page));
?>
<div class="wrap">
<h1>Top Customers by Orders</h1>
<table class="widefat fixed striped">
<thead>
<tr>
<th>User ID</th>
<th>Customer Name</th>
<th>Order Count</th>
</tr>
</thead>
<tbody>
<?php if ($results): ?>
<?php foreach ($results as $row):
$user = get_userdata($row->user_id);
?>
<tr>
<td><?php echo esc_html($row->user_id); ?></td>
<td><?php echo esc_html($user ? $user->display_name : 'Guest'); ?></td>
<td><?php echo esc_html($row->order_count); ?></td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr>
<td colspan="3">No records found.</td>
</tr>
<?php endif; ?>
</tbody>
</table>
<?php
// Pagination
if ($total_pages > 1):
echo '<div class="pagination" style="margin-top: 20px;">';
for ($i = 1; $i <= $total_pages; $i++) {
$class = ($i == $current_page) ? 'class="current"' : '';
echo '<a ' . $class . ' href="' . esc_url(add_query_arg('paged', $i)) . '" style="margin-right: 5px;">' . $i . '</a>';
}
echo '</div>';
endif;
?>
</div>
<?php
}
—
### Explanation:
– **Admin Page Registration:**
Adds a new menu under "Top Customers" in WordPress Admin.
– **Database Query with Pagination:**
– Retrieves the top customers based on order count.
– Uses `LIMIT` and `OFFSET` for pagination.
– Retrieves customer names using `get_userdata($row->user_id)`.
– **Table Display with Pagination:**
– Displays data in a formatted table.
– Includes pagination links to navigate pages.
—
### Result:
A WordPress admin page that lists the top customers with pagination, showing 10 customers per page.
Would you like any modifications, such as additional filters (e.g., date range)? π
### **Steps to Implement**
1. **Modify the "Total Orders" column** to make it clickable.
2. **Create a new admin sub-page** to display order notes.
3. **Fetch only admin-added order notes** from the `wp_comments` table (WooCommerce stores order notes as comments).
—
## **1. Modify the Orders Table to Add Clickable Links**
Update the `render_top_customers_page()` function to make the **Total Orders** count clickable.
function render_top_customers_page() {
global $wpdb;
$table_name = $wpdb->prefix . 'postmeta';
$meta_key = '_customer_user';
$per_page = 10;
$current_page = isset($_GET['paged']) ? max(1, intval($_GET['paged'])) : 1;
$offset = ($current_page - 1) * $per_page;
$total_records = $wpdb->get_var("
SELECT COUNT(DISTINCT meta_value) FROM $table_name WHERE meta_key = '$meta_key' AND meta_value > 0
");
$total_pages = ceil($total_records / $per_page);
$results = $wpdb->get_results($wpdb->prepare("
SELECT meta_value as user_id, COUNT(post_id) as order_count
FROM $table_name
WHERE meta_key = %s AND meta_value > 0
GROUP BY meta_value
ORDER BY order_count DESC
LIMIT %d, %d
", $meta_key, $offset, $per_page));
?>
<div class="wrap">
<h1>Top Customers by Orders</h1>
<table class="widefat fixed striped">
<thead>
<tr>
<th>User ID</th>
<th>Customer Name</th>
<th>Total Orders</th>
</tr>
</thead>
<tbody>
<?php if ($results): ?>
<?php foreach ($results as $row):
$user = get_userdata($row->user_id);
$customer_name = $user ? $user->display_name : 'Guest';
?>
<tr>
<td><?php echo esc_html($row->user_id); ?></td>
<td><?php echo esc_html($customer_name); ?></td>
<td>
<a href="<?php echo admin_url('admin.php?page=customer-order-notes&user_id=' . $row->user_id); ?>">
<?php echo esc_html($row->order_count); ?>
</a>
</td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr>
<td colspan="3">No records found.</td>
</tr>
<?php endif; ?>
</tbody>
</table>
<?php
// Pagination
if ($total_pages > 1):
echo '<div class="pagination" style="margin-top: 20px;">';
for ($i = 1; $i <= $total_pages; $i++) {
$class = ($i == $current_page) ? 'class="current"' : '';
echo '<a ' . $class . ' href="' . esc_url(add_query_arg('paged', $i)) . '" style="margin-right: 5px;">' . $i . '</a>';
}
echo '</div>';
endif;
?>
</div>
<?php
}
### **Changes Made:**
β
Added a clickable link on the **Total Orders** column.
β
The link takes the user to `admin.php?page=customer-order-notes&user_id={USER_ID}`.
—
## **2. Create a New Admin Page to Show Order Notes**
Now, create a new function to display order notes for a selected user.
function register_customer_order_notes_page() {
add_submenu_page(
null, // Hide it from the menu
'Customer Order Notes',
'Customer Order Notes',
'manage_options',
'customer-order-notes',
'render_customer_order_notes_page'
);
}
add_action('admin_menu', 'register_customer_order_notes_page');
function render_customer_order_notes_page() {
global $wpdb;
$user_id = isset($_GET['user_id']) ? intval($_GET['user_id']) : 0;
if (!$user_id) {
echo '<div class="error"><p>Invalid User ID.</p></div>';
return;
}
// Fetch user details
$user = get_userdata($user_id);
if (!$user) {
echo '<div class="error"><p>User not found.</p></div>';
return;
}
// Fetch order IDs for this user
$order_ids = $wpdb->get_col($wpdb->prepare("
SELECT post_id FROM {$wpdb->prefix}postmeta
WHERE meta_key = '_customer_user' AND meta_value = %d
", $user_id));
if (empty($order_ids)) {
echo '<div class="error"><p>No orders found for this user.</p></div>';
return;
}
$order_ids_placeholder = implode(',', array_fill(0, count($order_ids), '%d'));
// Fetch admin-added order notes
$query = $wpdb->prepare("
SELECT comment_ID, comment_post_ID, comment_content, comment_date
FROM {$wpdb->prefix}comments
WHERE comment_post_ID IN ($order_ids_placeholder)
AND comment_type = 'order_note'
AND comment_author = 'WooCommerce'
ORDER BY comment_date DESC
", ...$order_ids);
$notes = $wpdb->get_results($query);
?>
<div class="wrap">
<h1>Order Notes for <?php echo esc_html($user->display_name); ?></h1>
<table class="widefat fixed striped">
<thead>
<tr>
<th>Order ID</th>
<th>Note</th>
<th>Date</th>
</tr>
</thead>
<tbody>
<?php if ($notes): ?>
<?php foreach ($notes as $note): ?>
<tr>
<td><?php echo esc_html($note->comment_post_ID); ?></td>
<td><?php echo esc_html($note->comment_content); ?></td>
<td><?php echo esc_html($note->comment_date); ?></td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr>
<td colspan="3">No admin-added order notes found.</td>
</tr>
<?php endif; ?>
</tbody>
</table>
<p><a href="<?php echo admin_url('admin.php?page=top-customers-orders'); ?>" class="button">Back to Customers</a></p>
</div>
<?php
}
—
## **How It Works**
– When clicking **Total Orders**, the page redirects to `admin.php?page=customer-order-notes&user_id={USER_ID}`.
– It retrieves all **order IDs** for that customer from `wp_postmeta`.
– It then fetches **only admin-added** order notes (`comment_author = 'WooCommerce'`).
– Displays the **Order ID, Note, and Date** in a table.
– Includes a **Back to Customers** button.
—
## **Final Result**
### **1. Top Customers Page**
| User ID | Customer Name | Total Orders |
|———|————–|————–|
| 101 | John Doe | **[5](#)** |
| 102 | Jane Smith | **[3](#)** |
### **2. Clicking "Total Orders" Opens Order Notes**
| Order ID | Note | Date |
|———-|——|——|
| 2345 | "Admin changed shipping address." | 2025-02-20 |
| 6789 | "Refund processed for item." | 2025-02-18 |
—
### β
**Features Implemented**
β
Displays **Top Customers** with clickable total orders.
β
Retrieves **only admin-added order notes** from `wp_comments`.
β
Shows **order notes** with Order ID, Content, and Date.
β
Adds a **Back to Customers** button.
Would you like any additional filters, such as a **date range** or **search bar**? π
—
## **1. Update the Customers List to Show Only 50 Links**
Modify `render_top_customers_page()` to:
– Show only **50 customer rows per page**.
– Add a **"Next" button** to load more.
### **Code Update:**
“`php
function render_top_customers_page() {
global $wpdb;
$table_name = $wpdb->prefix . 'postmeta';
$meta_key = '_customer_user';
$per_page = 50; // Show 50 records per page
$current_page = isset($_GET['paged']) ? max(1, intval($_GET['paged'])) : 1;
$offset = ($current_page – 1) * $per_page;
$results = $wpdb->get_results($wpdb->prepare("
SELECT meta_value as user_id, COUNT(post_id) as order_count
FROM $table_name
WHERE meta_key = %s AND meta_value > 0
GROUP BY meta_value
ORDER BY order_count DESC
LIMIT %d, %d
", $meta_key, $offset, $per_page));
?>
Top Customers by Orders
User ID
Customer Name
Total Orders
β Only **50 pagination links** are displayed at a time.
β Clicking **Next** moves to the next set of 50 pagination links.
β Clicking **Prev** moves back to the previous set of 50 pagination links.
—
### **Solution**
– We'll show only **50 page numbers** at a time.
– Clicking **Next** loads the **next 50 pages**, and **Prev** loads the previous 50.
– If total pages = **500**, we show **1-50**, then **51-100**, and so on.
—
### **Updated Pagination Code**
function custom_pagination($total_pages, $current_page, $base_url) {
$links_per_set = 50; // Show 50 pagination links at a time
$current_set = ceil($current_page / $links_per_set); // Determine the set
$start_page = (($current_set - 1) * $links_per_set) + 1; // First page of the set
$end_page = min($start_page + $links_per_set - 1, $total_pages); // Last page of the set
echo '<div class="pagination">';
// Prev button (disabled if on first set)
if ($current_set > 1) {
$prev_set_page = $start_page - 1;
echo '<a href="' . esc_url(add_query_arg('paged', $prev_set_page, $base_url)) . '">Β« Prev 50</a>';
}
// Page number links
for ($i = $start_page; $i <= $end_page; $i++) {
$active_class = ($i == $current_page) ? 'class="active"' : '';
echo '<a ' . $active_class . ' href="' . esc_url(add_query_arg('paged', $i, $base_url)) . '">' . $i . '</a>';
}
// Next button (disabled if on last set)
if ($end_page < $total_pages) {
$next_set_page = $end_page + 1;
echo '<a href="' . esc_url(add_query_arg('paged', $next_set_page, $base_url)) . '">Next 50 Β»</a>';
}
echo '</div>';
}
—
### **Usage Example**
Inside your admin page:
$total_pages = ceil($wpdb->get_var("SELECT COUNT(DISTINCT meta_value) FROM {$wpdb->prefix}postmeta WHERE meta_key = '_customer_user' AND meta_value > 0") / 25);
custom_pagination($total_pages, $current_page, admin_url('admin.php?page=top_customers'));
—
### **How It Works**
– If **current page = 1**, it shows **1 to 50** pagination links.
– Clicking **Next 50 Β»** shows **51 to 100**.
– Clicking **Β« Prev 50** moves back.
– Page numbers are clickable, and clicking a number takes you to that page.
Let me know if you need any changes! π
### **Updated Pagination Code with Spacing**
function custom_pagination($total_pages, $current_page, $base_url) {
$links_per_set = 50; // Show 50 pagination links at a time
$current_set = ceil($current_page / $links_per_set); // Determine the set
$start_page = (($current_set - 1) * $links_per_set) + 1; // First page of the set
$end_page = min($start_page + $links_per_set - 1, $total_pages); // Last page of the set
echo '<div class="pagination" style="margin-top: 20px; text-align: center;">';
// Prev button (disabled if on first set)
if ($current_set > 1) {
$prev_set_page = $start_page - 1;
echo '<a href="' . esc_url(add_query_arg('paged', $prev_set_page, $base_url)) . '" style="margin-right: 10px; padding: 5px 10px; background: #0073aa; color: #fff; text-decoration: none; border-radius: 4px;">Β« Prev 50</a>';
}
// Page number links
for ($i = $start_page; $i <= $end_page; $i++) {
$active_class = ($i == $current_page) ? 'background: #0073aa; color: #fff;' : 'background: #f7f7f7; color: #333;';
echo '<a href="' . esc_url(add_query_arg('paged', $i, $base_url)) . '" style="display: inline-block; margin: 5px; padding: 5px 10px; ' . $active_class . ' text-decoration: none; border-radius: 4px;">' . $i . '</a>';
}
// Next button (disabled if on last set)
if ($end_page < $total_pages) {
$next_set_page = $end_page + 1;
echo '<a href="' . esc_url(add_query_arg('paged', $next_set_page, $base_url)) . '" style="margin-left: 10px; padding: 5px 10px; background: #0073aa; color: #fff; text-decoration: none; border-radius: 4px;">Next 50 Β»</a>';
}
echo '</div>';
}
—
### **Improvements**
β
**Added spacing** between pagination links using `margin: 5px;`
β
**Styled Prev/Next buttons** to be visually distinct
β
**Highlighted the active page** with a blue background
### **Final Look**
π **Prev 50** | 1 | 2 | 3 | … | 50 | **Next 50**
(With enough space to make it readable and clickable)
Let me know if you need further adjustments! π
β Add a **search box** where the admin can enter a User ID or Name.
β Modify the SQL query to filter by the search input.
β Keep the pagination working as expected.
—
### **π Updated Code:**
function render_top_customers_page() {
global $wpdb;
$table_name = $wpdb->prefix . 'postmeta';
$meta_key = '_customer_user';
$per_page = 50;
$current_page = isset($_GET['paged']) ? max(1, intval($_GET['paged'])) : 1;
$offset = ($current_page - 1) * $per_page;
$search_query = isset($_GET['search_user']) ? sanitize_text_field($_GET['search_user']) : '';
// Search Condition
$search_sql = '';
$params = [$meta_key, $offset, $per_page];
if (!empty($search_query)) {
if (is_numeric($search_query)) {
$search_sql = " AND meta_value = %d ";
array_splice($params, 1, 0, [$search_query]); // Insert search query in params
} else {
$search_sql = " AND meta_value IN (SELECT ID FROM {$wpdb->users} WHERE display_name LIKE %s) ";
array_splice($params, 1, 0, ['%' . $wpdb->esc_like($search_query) . '%']);
}
}
// Query to get users
$results = $wpdb->get_results($wpdb->prepare("
SELECT meta_value as user_id, COUNT(post_id) as order_count
FROM $table_name
WHERE meta_key = %s AND meta_value > 0 $search_sql
GROUP BY meta_value
ORDER BY order_count DESC
LIMIT %d, %d
", ...$params));
// Total pages calculation
$total_count = $wpdb->get_var($wpdb->prepare("
SELECT COUNT(DISTINCT meta_value) FROM $table_name
WHERE meta_key = %s AND meta_value > 0 $search_sql
", ...array_slice($params, 0, 1))); // Use only search params
$total_pages = ceil($total_count / $per_page);
?>
<div class="wrap">
<h1>Top Customers by Orders</h1>
<!-- Search Form -->
<form method="GET" action="">
<input type="hidden" name="page" value="top_customers">
<input type="text" name="search_user" value="<?php echo esc_attr($search_query); ?>" placeholder="Search by User ID or Name">
<button type="submit">Search</button>
<?php if (!empty($search_query)) : ?>
<a href="<?php echo esc_url(admin_url('admin.php?page=top_customers')); ?>">Clear Search</a>
<?php endif; ?>
</form>
<table class="widefat fixed striped">
<thead>
<tr>
<th>User ID</th>
<th>Customer Name</th>
<th>Total Orders</th>
</tr>
</thead>
<tbody>
<?php if ($results): ?>
<?php foreach ($results as $row):
$user = get_userdata($row->user_id);
$customer_name = $user ? $user->display_name : 'Guest';
?>
<tr>
<td><?php echo esc_html($row->user_id); ?></td>
<td><?php echo esc_html($customer_name); ?></td>
<td><?php echo esc_html($row->order_count); ?></td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr><td colspan="3">No records found.</td></tr>
<?php endif; ?>
</tbody>
</table>
<?php custom_pagination($total_pages, $current_page, admin_url('admin.php?page=top_customers&search_user=' . urlencode($search_query))); ?>
</div>
<?php
}
—
### **πΉ Features Added**
β
**Search by User ID or Name**
β
**Preserves Pagination with Search**
β
**"Clear Search" Button to Reset**
πΉ **Example Searches:**
– Enter `123` β Filters by **User ID 123**
– Enter `John` β Filters users whose **name contains "John"**
Now you have **search + pagination** working perfectly! π Let me know if you need more tweaks.
### **π Fixing the Issue:**
1. Ensure the search condition applies correctly to **both queries** (results & total count).
2. Make sure `meta_value` is treated as an **integer** when searching by ID.
3. When searching by name, ensure that only matching user IDs are used.
—
### **πΉ Fixed Code**
function render_top_customers_page() {
global $wpdb;
$table_name = $wpdb->prefix . 'postmeta';
$meta_key = '_customer_user';
$per_page = 50;
$current_page = isset($_GET['paged']) ? max(1, intval($_GET['paged'])) : 1;
$offset = ($current_page - 1) * $per_page;
$search_query = isset($_GET['search_user']) ? sanitize_text_field($_GET['search_user']) : '';
// Search condition handling
$search_sql = '';
$search_params = [$meta_key];
if (!empty($search_query)) {
if (is_numeric($search_query)) {
// If searching by user ID
$search_sql = " AND meta_value = %d ";
$search_params[] = intval($search_query);
} else {
// If searching by username
$user_ids = $wpdb->get_col($wpdb->prepare("SELECT ID FROM {$wpdb->users} WHERE display_name LIKE %s", '%' . $wpdb->esc_like($search_query) . '%'));
if (!empty($user_ids)) {
$user_ids_placeholders = implode(',', array_fill(0, count($user_ids), '%d'));
$search_sql = " AND meta_value IN ($user_ids_placeholders) ";
$search_params = array_merge($search_params, $user_ids);
} else {
$search_sql = " AND meta_value IN (NULL) "; // Force empty result if no user matches
}
}
}
// Query to get users
$query = "
SELECT meta_value as user_id, COUNT(post_id) as order_count
FROM $table_name
WHERE meta_key = %s AND meta_value > 0 $search_sql
GROUP BY meta_value
ORDER BY order_count DESC
LIMIT %d, %d
";
$search_params[] = $offset;
$search_params[] = $per_page;
$results = $wpdb->get_results($wpdb->prepare($query, ...$search_params));
// Fix total count query
$count_query = "
SELECT COUNT(DISTINCT meta_value) FROM $table_name
WHERE meta_key = %s AND meta_value > 0 $search_sql
";
$total_count = $wpdb->get_var($wpdb->prepare($count_query, ...array_slice($search_params, 0, count($search_params) - 2))); // Exclude limit params
$total_pages = ceil($total_count / $per_page);
?>
<div class="wrap">
<h1>Top Customers by Orders</h1>
<!-- Search Form -->
<form method="GET" action="">
<input type="hidden" name="page" value="top_customers">
<input type="text" name="search_user" value="<?php echo esc_attr($search_query); ?>" placeholder="Search by User ID or Name">
<button type="submit">Search</button>
<?php if (!empty($search_query)) : ?>
<a href="<?php echo esc_url(admin_url('admin.php?page=top_customers')); ?>">Clear Search</a>
<?php endif; ?>
</form>
<table class="widefat fixed striped">
<thead>
<tr>
<th>User ID</th>
<th>Customer Name</th>
<th>Total Orders</th>
</tr>
</thead>
<tbody>
<?php if ($results): ?>
<?php foreach ($results as $row):
$user = get_userdata($row->user_id);
$customer_name = $user ? $user->display_name : 'Guest';
?>
<tr>
<td><?php echo esc_html($row->user_id); ?></td>
<td><?php echo esc_html($customer_name); ?></td>
<td><?php echo esc_html($row->order_count); ?></td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr><td colspan="3">No records found.</td></tr>
<?php endif; ?>
</tbody>
</table>
<?php custom_pagination($total_pages, $current_page, admin_url('admin.php?page=top_customers&search_user=' . urlencode($search_query))); ?>
</div>
<?php
}
—
### **πΉ Fixes & Enhancements**
β
**Now correctly filters by User ID or Name**
β
**Correctly calculates total pages** (Fixes `10503 results` issue)
β
**Prevents incorrect matching by ensuring exact ID filtering**
β
**Avoids empty searches breaking the pagination**
Let me know if you need any modifications! π
add_submenu_page(
null, // Hide it from the menu
'Customer Order Notes',
'Customer Order Notes',
'manage_options',
'customer-order-notes',
'render_customer_order_notes_page'
);
}
add_action('admin_menu', 'register_customer_order_notes_page');
function render_customer_order_notes_page() {
global $wpdb;
$user_id = isset($_GET['user_id']) ? intval($_GET['user_id']) : 0;
if (!$user_id) {
echo 'Invalid User ID.';
return;
}
// Fetch user details
$user = get_userdata($user_id);
if (!$user) {
echo 'User not found.';
return;
}
// Fetch order IDs for this user
$order_ids = $wpdb->get_col($wpdb->prepare("
SELECT post_id FROM {$wpdb->prefix}postmeta
WHERE meta_key = '_customer_user' AND meta_value = %d
", $user_id));
if (empty($order_ids)) {
echo 'No orders found for this user.';
return;
}
$order_ids_placeholder = implode(',', array_fill(0, count($order_ids), '%d'));
// Fetch admin-added order notes
$query = $wpdb->prepare("
SELECT comment_ID, comment_post_ID, comment_content, comment_date
FROM {$wpdb->prefix}comments
WHERE comment_post_ID IN ($order_ids_placeholder)
AND comment_type = 'order_note'
AND comment_author = 'WooCommerce'
ORDER BY comment_date DESC
", …$order_ids);
$notes = $wpdb->get_results($query);
?>
Order Notes for
Order ID
Note
Date
No admin-added order notes found.
—
### **πΉ Updated Code**
function render_customer_order_notes_page() {
global $wpdb;
$user_id = isset($_GET['user_id']) ? intval($_GET['user_id']) : 0;
if (!$user_id) {
echo '<div class="error"><p>Invalid User ID.</p></div>';
return;
}
// Fetch user details
$user = get_userdata($user_id);
if (!$user) {
echo '<div class="error"><p>User not found.</p></div>';
return;
}
// Fetch order IDs for this user
$order_ids = $wpdb->get_col($wpdb->prepare("
SELECT post_id FROM {$wpdb->prefix}postmeta
WHERE meta_key = '_customer_user' AND meta_value = %d
", $user_id));
if (empty($order_ids)) {
echo '<div class="error"><p>No orders found for this user.</p></div>';
return;
}
$order_ids_placeholder = implode(',', array_fill(0, count($order_ids), '%d'));
// Pagination setup
$per_page = 500;
$current_page = isset($_GET['paged']) ? max(1, intval($_GET['paged'])) : 1;
$offset = ($current_page - 1) * $per_page;
// Get total count
$count_query = $wpdb->prepare("
SELECT COUNT(*) FROM {$wpdb->prefix}comments
WHERE comment_post_ID IN ($order_ids_placeholder)
AND comment_type = 'order_note'
AND comment_author = 'WooCommerce'
", ...$order_ids);
$total_notes = $wpdb->get_var($count_query);
$total_pages = ceil($total_notes / $per_page);
// Fetch admin-added order notes
$query = $wpdb->prepare("
SELECT comment_ID, comment_post_ID, comment_content, comment_date
FROM {$wpdb->prefix}comments
WHERE comment_post_ID IN ($order_ids_placeholder)
AND comment_type = 'order_note'
AND comment_author = 'WooCommerce'
ORDER BY comment_date DESC
LIMIT %d, %d
", ...array_merge($order_ids, [$offset, $per_page]));
$notes = $wpdb->get_results($query);
?>
<div class="wrap">
<h1>Order Notes for <?php echo esc_html($user->display_name); ?></h1>
<table class="widefat fixed striped">
<thead>
<tr>
<th>Order ID</th>
<th>Note</th>
<th>Date</th>
</tr>
</thead>
<tbody>
<?php if ($notes): ?>
<?php foreach ($notes as $note): ?>
<tr>
<td><?php echo esc_html($note->comment_post_ID); ?></td>
<td><?php echo esc_html($note->comment_content); ?></td>
<td><?php echo esc_html($note->comment_date); ?></td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr>
<td colspan="3">No admin-added order notes found.</td>
</tr>
<?php endif; ?>
</tbody>
</table>
<!-- Pagination -->
<div class="tablenav">
<div class="tablenav-pages">
<?php if ($total_pages > 1): ?>
<?php
$base_url = admin_url("admin.php?page=customer-order-notes&user_id={$user_id}");
$prev_page = $current_page > 1 ? $current_page - 1 : 1;
$next_page = $current_page < $total_pages ? $current_page + 1 : $total_pages;
?>
<?php if ($current_page > 1): ?>
<a class="button" href="<?php echo esc_url($base_url . '&paged=' . $prev_page); ?>">β Previous</a>
<?php endif; ?>
<span class="pagination-links"> Page <?php echo $current_page; ?> of <?php echo $total_pages; ?> </span>
<?php if ($current_page < $total_pages): ?>
<a class="button" href="<?php echo esc_url($base_url . '&paged=' . $next_page); ?>">Next β</a>
<?php endif; ?>
<?php endif; ?>
</div>
</div>
<p><a href="<?php echo admin_url('admin.php?page=top-customers-orders'); ?>" class="button">Back to Customers</a></p>
</div>
<?php
}
—
### **πΉ Features & Fixes**
β
**Pagination Added:** Displays **500 records per page**
β
**Total Notes Count Fixed:** Ensures correct results
β
**Order IDs Prepared Safely:** Prevents SQL errors
β
**Next & Previous Buttons:** Only appear when needed
Let me know if you need further changes! π
### **πΉ Updated Code to Display Time in WordPress Selected Format**
Replace:
<td><?php echo esc_html($note->comment_date); ?></td>
With:
<td><?php echo esc_html(date_i18n(get_option('date_format') . ' ' . get_option('time_format'), strtotime($note->comment_date))); ?></td>
—
### **πΉ Full Updated Code (Time in WP Format)**
function render_customer_order_notes_page() {
global $wpdb;
$user_id = isset($_GET['user_id']) ? intval($_GET['user_id']) : 0;
if (!$user_id) {
echo '<div class="error"><p>Invalid User ID.</p></div>';
return;
}
$user = get_userdata($user_id);
if (!$user) {
echo '<div class="error"><p>User not found.</p></div>';
return;
}
$order_ids = $wpdb->get_col($wpdb->prepare("
SELECT post_id FROM {$wpdb->prefix}postmeta
WHERE meta_key = '_customer_user' AND meta_value = %d
", $user_id));
if (empty($order_ids)) {
echo '<div class="error"><p>No orders found for this user.</p></div>';
return;
}
$order_ids_placeholder = implode(',', array_fill(0, count($order_ids), '%d'));
$per_page = 500;
$current_page = isset($_GET['paged']) ? max(1, intval($_GET['paged'])) : 1;
$offset = ($current_page - 1) * $per_page;
$count_query = $wpdb->prepare("
SELECT COUNT(*) FROM {$wpdb->prefix}comments
WHERE comment_post_ID IN ($order_ids_placeholder)
AND comment_type = 'order_note'
AND comment_author = 'WooCommerce'
", ...$order_ids);
$total_notes = $wpdb->get_var($count_query);
$total_pages = ceil($total_notes / $per_page);
$query = $wpdb->prepare("
SELECT comment_ID, comment_post_ID, comment_content, comment_date
FROM {$wpdb->prefix}comments
WHERE comment_post_ID IN ($order_ids_placeholder)
AND comment_type = 'order_note'
AND comment_author = 'WooCommerce'
ORDER BY comment_date DESC
LIMIT %d, %d
", ...array_merge($order_ids, [$offset, $per_page]));
$notes = $wpdb->get_results($query);
?>
<div class="wrap">
<h1>Order Notes for <?php echo esc_html($user->display_name); ?></h1>
<table class="widefat fixed striped">
<thead>
<tr>
<th>Order ID</th>
<th>Note</th>
<th>Date</th>
</tr>
</thead>
<tbody>
<?php if ($notes): ?>
<?php foreach ($notes as $note): ?>
<tr>
<td><?php echo esc_html($note->comment_post_ID); ?></td>
<td><?php echo esc_html($note->comment_content); ?></td>
<td><?php echo esc_html(date_i18n(get_option('date_format') . ' ' . get_option('time_format'), strtotime($note->comment_date))); ?></td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr>
<td colspan="3">No admin-added order notes found.</td>
</tr>
<?php endif; ?>
</tbody>
</table>
<div class="tablenav">
<div class="tablenav-pages">
<?php if ($total_pages > 1): ?>
<?php
$base_url = admin_url("admin.php?page=customer-order-notes&user_id={$user_id}");
$prev_page = $current_page > 1 ? $current_page - 1 : 1;
$next_page = $current_page < $total_pages ? $current_page + 1 : $total_pages;
?>
<?php if ($current_page > 1): ?>
<a class="button" href="<?php echo esc_url($base_url . '&paged=' . $prev_page); ?>">β Previous</a>
<?php endif; ?>
<span class="pagination-links"> Page <?php echo $current_page; ?> of <?php echo $total_pages; ?> </span>
<?php if ($current_page < $total_pages): ?>
<a class="button" href="<?php echo esc_url($base_url . '&paged=' . $next_page); ?>">Next β</a>
<?php endif; ?>
<?php endif; ?>
</div>
</div>
<p><a href="<?php echo admin_url('admin.php?page=top-customers-orders'); ?>" class="button">Back to Customers</a></p>
</div>
<?php
}
—
### **πΉ Changes & Features**
β
**Displays time in WordPress selected format**
β
**Uses `date_i18n()` for localization**
β
**Keeps pagination with 500 records per page**
β
**Ensures security with proper escaping and validation**
Let me know if you need further modifications! π
<td><?php echo esc_html(date_i18n(get_option('date_format') . ' ' . get_option('time_format'), strtotime($note->comment_date))); ?></td>
with:
<td><?php echo esc_html(wp_date(get_option('date_format') . ' ' . get_option('time_format'), strtotime($note->comment_date))); ?></td>
—
### **πΉ Updated Code with `wp_date()`**
function render_customer_order_notes_page() {
global $wpdb;
$user_id = isset($_GET['user_id']) ? intval($_GET['user_id']) : 0;
if (!$user_id) {
echo '<div class="error"><p>Invalid User ID.</p></div>';
return;
}
$user = get_userdata($user_id);
if (!$user) {
echo '<div class="error"><p>User not found.</p></div>';
return;
}
$order_ids = $wpdb->get_col($wpdb->prepare("
SELECT post_id FROM {$wpdb->prefix}postmeta
WHERE meta_key = '_customer_user' AND meta_value = %d
", $user_id));
if (empty($order_ids)) {
echo '<div class="error"><p>No orders found for this user.</p></div>';
return;
}
$order_ids_placeholder = implode(',', array_fill(0, count($order_ids), '%d'));
$per_page = 500;
$current_page = isset($_GET['paged']) ? max(1, intval($_GET['paged'])) : 1;
$offset = ($current_page - 1) * $per_page;
$count_query = $wpdb->prepare("
SELECT COUNT(*) FROM {$wpdb->prefix}comments
WHERE comment_post_ID IN ($order_ids_placeholder)
AND comment_type = 'order_note'
AND comment_author = 'WooCommerce'
", ...$order_ids);
$total_notes = $wpdb->get_var($count_query);
$total_pages = ceil($total_notes / $per_page);
$query = $wpdb->prepare("
SELECT comment_ID, comment_post_ID, comment_content, comment_date
FROM {$wpdb->prefix}comments
WHERE comment_post_ID IN ($order_ids_placeholder)
AND comment_type = 'order_note'
AND comment_author = 'WooCommerce'
ORDER BY comment_date DESC
LIMIT %d, %d
", ...array_merge($order_ids, [$offset, $per_page]));
$notes = $wpdb->get_results($query);
?>
<div class="wrap">
<h1>Order Notes for <?php echo esc_html($user->display_name); ?></h1>
<table class="widefat fixed striped">
<thead>
<tr>
<th>Order ID</th>
<th>Note</th>
<th>Date</th>
</tr>
</thead>
<tbody>
<?php if ($notes): ?>
<?php foreach ($notes as $note): ?>
<tr>
<td><?php echo esc_html($note->comment_post_ID); ?></td>
<td><?php echo esc_html($note->comment_content); ?></td>
<td><?php echo esc_html(wp_date(get_option('date_format') . ' ' . get_option('time_format'), strtotime($note->comment_date))); ?></td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr>
<td colspan="3">No admin-added order notes found.</td>
</tr>
<?php endif; ?>
</tbody>
</table>
<div class="tablenav">
<div class="tablenav-pages">
<?php if ($total_pages > 1): ?>
<?php
$base_url = admin_url("admin.php?page=customer-order-notes&user_id={$user_id}");
$prev_page = $current_page > 1 ? $current_page - 1 : 1;
$next_page = $current_page < $total_pages ? $current_page + 1 : $total_pages;
?>
<?php if ($current_page > 1): ?>
<a class="button" href="<?php echo esc_url($base_url . '&paged=' . $prev_page); ?>">β Previous</a>
<?php endif; ?>
<span class="pagination-links"> Page <?php echo $current_page; ?> of <?php echo $total_pages; ?> </span>
<?php if ($current_page < $total_pages): ?>
<a class="button" href="<?php echo esc_url($base_url . '&paged=' . $next_page); ?>">Next β</a>
<?php endif; ?>
<?php endif; ?>
</div>
</div>
<p><a href="<?php echo admin_url('admin.php?page=top-customers-orders'); ?>" class="button">Back to Customers</a></p>
</div>
<?php
}
—
### **πΉ Why `wp_date()`?**
β
Uses WordPress timezone settings
β
Respects the date and time format selected in **Settings β General**
β
More accurate than `date_i18n()`
Let me know if you need any tweaks! π