Skip to content

Instantly share code, notes, and snippets.

@todditron
Last active August 11, 2025 21:15
Show Gist options
  • Save todditron/080a0a752ee33f4d37ed5dd20b234b05 to your computer and use it in GitHub Desktop.
Save todditron/080a0a752ee33f4d37ed5dd20b234b05 to your computer and use it in GitHub Desktop.
<?php
/**
* Plugin Name: WLM Performance Debug
* Description: Debug WishList Member performance issues, especially the "Protect Existing Now" corruption bug
* Version: 1.0.4
*
* Installation:
* 1. Save this file as: /wp-content/plugins/wlm-performance-debug/wlm-performance-debug.php
* 2. Activate the plugin from WordPress admin
* 3. Save any post to see debug output
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
class WLM_Performance_Debug {
private $query_counts = [];
private $query_samples = [];
private $start_time = 0;
private $is_tracking = false;
public function __construct() {
add_action('init', [$this, 'init']);
}
public function init() {
// Only run for logged-in users with edit capabilities
if (!current_user_can('edit_posts')) {
return;
}
add_action('wp_insert_post', [$this, 'start_tracking_save'], 1, 3);
add_action('admin_notices', [$this, 'display_performance_results']);
add_action('wp_footer', [$this, 'display_performance_results_frontend']);
}
public function start_tracking_save($post_id, $post, $update) {
// Skip autosaves and revisions
if (wp_is_post_autosave($post_id) || wp_is_post_revision($post_id)) {
return;
}
$this->reset_tracking();
$this->is_tracking = true;
$this->start_time = microtime(true);
// Start tracking queries
add_filter('query', [$this, 'track_query']);
// Schedule results display after save completes
add_action('shutdown', function() use ($post_id, $post) {
$this->finalize_tracking($post_id, $post);
}, 1);
}
private function reset_tracking() {
$this->query_counts = [
'total_queries' => 0,
'wlm_contentlevels_deletes' => 0,
'wlm_contentsched_deletes' => 0,
'wlm_cache_updates' => 0,
'wlm_protection_checks' => 0,
'wlm_options_reads' => 0,
'wlm_api_calls' => 0,
'other_wlm_queries' => 0
];
$this->query_samples = [
'wlm_contentlevels_deletes' => [],
'wlm_contentsched_deletes' => [],
'wlm_cache_updates' => [],
'wlm_protection_checks' => [],
'wlm_options_reads' => [],
'wlm_api_calls' => [],
'other_wlm_queries' => []
];
}
public function track_query($query) {
if (!$this->is_tracking) {
return $query;
}
$time_elapsed = microtime(true) - $this->start_time;
// Only count queries after initial processing starts
if ($time_elapsed > 0.05) {
$this->query_counts['total_queries']++;
// Track and sample specific WLM operations
if (strpos($query, 'wp_wlm_contentlevels') !== false && strpos($query, 'DELETE') !== false) {
$this->query_counts['wlm_contentlevels_deletes']++;
$this->add_sample('wlm_contentlevels_deletes', $query);
}
elseif (strpos($query, 'wp_wlcc_contentsched') !== false && strpos($query, 'DELETE') !== false) {
$this->query_counts['wlm_contentsched_deletes']++;
$this->add_sample('wlm_contentsched_deletes', $query);
}
elseif (strpos($query, 'wlm_cache_group_membership_content') !== false) {
$this->query_counts['wlm_cache_updates']++;
$this->add_sample('wlm_cache_updates', $query);
}
elseif (strpos($query, 'wp_wlm_contentlevels') !== false || strpos($query, 'wp_wlcc_contentsched') !== false) {
$this->query_counts['wlm_protection_checks']++;
$this->add_sample('wlm_protection_checks', $query);
}
elseif (strpos($query, 'wp_wlm_options') !== false) {
$this->query_counts['wlm_options_reads']++;
$this->add_sample('wlm_options_reads', $query);
}
elseif (strpos($query, 'wlmapi_get_levels') !== false || (strpos($query, 'wp_wlm') !== false && strpos($query, 'levels') !== false)) {
$this->query_counts['wlm_api_calls']++;
$this->add_sample('wlm_api_calls', $query);
}
elseif (strpos($query, 'wlm') !== false || strpos($query, 'WLM') !== false) {
$this->query_counts['other_wlm_queries']++;
$this->add_sample('other_wlm_queries', $query);
}
}
return $query;
}
private function add_sample($category, $query) {
// Keep max 3 samples per category to avoid overwhelming output
if (count($this->query_samples[$category]) < 3) {
$this->query_samples[$category][] = $this->sanitize_query_sample($query);
}
}
private function sanitize_query_sample($query) {
// Truncate long queries and remove sensitive data
$clean_query = preg_replace('/\s+/', ' ', trim($query));
return substr($clean_query, 0, 200) . (strlen($clean_query) > 200 ? '...' : '');
}
private function finalize_tracking($post_id, $post) {
global $wpdb;
if (!$this->is_tracking) {
return;
}
$this->is_tracking = false;
$total_time = microtime(true) - $this->start_time;
// Store results in transient for display
$results = [
'post_id' => $post_id,
'post_title' => $post->post_title,
'post_type' => $post->post_type,
'total_time' => round($total_time, 3),
'query_counts' => $this->query_counts,
'query_samples' => $this->query_samples,
'timestamp' => current_time('mysql')
];
// Add performance assessment
$total_wlm = array_sum(array_slice($this->query_counts, 1)); // Exclude total_queries
$results['performance_status'] = $this->assess_performance($total_wlm);
// Add membership level context
if (function_exists('wlmapi_get_levels')) {
$levels_result = wlmapi_get_levels();
if (isset($levels_result['success']) && $levels_result['success'] == 1) {
$results['membership_levels'] = count($levels_result['levels']['level']);
}
}
// Get assigned levels for this specific post
$assigned_levels = $wpdb->get_results($wpdb->prepare(
"SELECT DISTINCT level_id FROM {$wpdb->prefix}wlm_contentlevels WHERE content_id = %d AND level_id != 'Inherit' AND level_id != 'Protection'",
$post_id
), ARRAY_A);
$results['assigned_levels'] = wp_list_pluck($assigned_levels, 'level_id');
// Add critical debugging info for developers
$results['debug_info'] = $this->gather_debug_info($post_id, $post);
set_transient('wlm_debug_results_' . get_current_user_id(), $results, 300); // 5 minutes
}
private function gather_debug_info($post_id, $post) {
global $wpdb;
$debug_info = [];
// WLM uses its own tables, not post meta - skip meta check
// Check WLM content levels for this post
$content_levels = $wpdb->get_results($wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}wlm_contentlevels WHERE content_id = %d",
$post_id
), ARRAY_A);
$debug_info['current_content_levels'] = $content_levels;
// Check for "Inherit" levels (these might be the corruption indicator)
$inherit_levels = $wpdb->get_results($wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}wlm_contentlevels WHERE content_id = %d AND level_id = 'Inherit'",
$post_id
), ARRAY_A);
$debug_info['inherit_levels'] = $inherit_levels;
// Check for content scheduling
$content_sched = $wpdb->get_results($wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}wlcc_contentsched WHERE post_id = %d",
$post_id
), ARRAY_A);
$debug_info['content_scheduling'] = $content_sched;
// Check for child posts (inheritance might be causing mass operations)
if ($post->post_type === 'course') {
$children = get_posts([
'post_type' => 'any',
'post_parent' => $post_id,
'numberposts' => -1,
'post_status' => 'any'
]);
$debug_info['child_posts_count'] = count($children);
if (count($children) > 0) {
$debug_info['child_post_ids'] = wp_list_pluck($children, 'ID');
}
}
// Check WLM options that might indicate bulk operation mode
$wlm_options_keys = [
'wlm_bulk_operation_mode',
'wlm_protect_existing_mode',
'wlm_inheritance_processing',
'wlm_batch_processing'
];
foreach ($wlm_options_keys as $key) {
$value = get_option($key);
if ($value !== false) {
$debug_info['wlm_options'][$key] = $value;
}
}
// Check for any WLM transients that might indicate ongoing operations
$wlm_transients = [];
$transient_keys = [
'wlm_bulk_protect',
'wlm_protect_existing',
'wlm_inheritance_update',
'wlm_content_protection_update'
];
foreach ($transient_keys as $key) {
$value = get_transient($key);
if ($value !== false) {
$wlm_transients[$key] = $value;
}
}
$debug_info['active_transients'] = $wlm_transients;
// Get call stack info if available
if (function_exists('debug_backtrace')) {
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 15);
$wlm_functions = [];
foreach ($backtrace as $trace) {
if (isset($trace['function']) &&
(strpos($trace['function'], 'wlm') !== false ||
strpos($trace['function'], 'WLM') !== false ||
(isset($trace['file']) && strpos($trace['file'], 'wishlist-member') !== false))) {
$wlm_functions[] = [
'function' => $trace['function'] ?? 'unknown',
'file' => basename($trace['file'] ?? 'unknown'),
'line' => $trace['line'] ?? 'unknown'
];
}
}
$debug_info['wlm_call_stack'] = $wlm_functions;
}
return $debug_info;
}
private function assess_performance($total_wlm_queries) {
if ($total_wlm_queries > 100) {
return [
'level' => 'critical',
'message' => 'CRITICAL: Likely affected by "Protect Existing Now" corruption bug'
];
} elseif ($total_wlm_queries > 10) {
return [
'level' => 'warning',
'message' => 'WARNING: Excessive WLM processing detected'
];
} else {
return [
'level' => 'good',
'message' => 'Performance looks normal'
];
}
}
public function display_performance_results() {
if (!is_admin()) {
return;
}
$results = get_transient('wlm_debug_results_' . get_current_user_id());
if (!$results) {
return;
}
// Delete the transient so it only shows once
delete_transient('wlm_debug_results_' . get_current_user_id());
$this->render_results($results);
}
public function display_performance_results_frontend() {
if (is_admin() || !current_user_can('edit_posts')) {
return;
}
$results = get_transient('wlm_debug_results_' . get_current_user_id());
if (!$results) {
return;
}
// Delete the transient so it only shows once
delete_transient('wlm_debug_results_' . get_current_user_id());
echo '<div style="position:fixed;top:32px;right:20px;z-index:99999;max-width:600px;">';
$this->render_results($results);
echo '</div>';
}
private function render_results($results) {
$status_colors = [
'critical' => '#dc3232',
'warning' => '#ffb900',
'good' => '#00a32a'
];
$status_color = $status_colors[$results['performance_status']['level']];
$total_wlm = array_sum(array_slice($results['query_counts'], 1));
?>
<div class="notice" style="border-left: 4px solid <?= $status_color ?>; background: white; padding: 15px; margin: 10px 0; box-shadow: 0 2px 5px rgba(0,0,0,0.1);">
<h3 style="margin-top: 0; color: <?= $status_color ?>;">
πŸ” WLM Performance Debug Results
</h3>
<div style="margin-bottom: 15px;">
<strong>Post:</strong> <?= esc_html($results['post_title']) ?> (ID: <?= $results['post_id'] ?>, Type: <?= $results['post_type'] ?>)<br>
<strong>Save Time:</strong> <?= $results['total_time'] ?>s<br>
<strong>Status:</strong> <span style="color: <?= $status_color ?>; font-weight: bold;">
<?= esc_html($results['performance_status']['message']) ?>
</span>
</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 15px; margin-bottom: 15px;">
<div>
<strong>Query Summary:</strong><br>
Total Queries: <?= $results['query_counts']['total_queries'] ?><br>
Total WLM Queries: <?= $total_wlm ?><br>
<?php if ($results['query_counts']['total_queries'] > 0): ?>
WLM Percentage: <?= round(($total_wlm / $results['query_counts']['total_queries']) * 100, 1) ?>%
<?php endif; ?>
</div>
<div>
<strong>WLM Breakdown:</strong><br>
Content Deletes: <?= $results['query_counts']['wlm_contentlevels_deletes'] ?><br>
Schedule Deletes: <?= $results['query_counts']['wlm_contentsched_deletes'] ?><br>
Protection Checks: <?= $results['query_counts']['wlm_protection_checks'] ?><br>
Options Reads: <?= $results['query_counts']['wlm_options_reads'] ?><br>
Cache Updates: <?= $results['query_counts']['wlm_cache_updates'] ?><br>
Other WLM: <?= $results['query_counts']['other_wlm_queries'] ?>
</div>
</div>
<?php if (isset($results['assigned_levels']) && isset($results['membership_levels'])): ?>
<div style="margin-bottom: 15px;">
<strong>Membership Levels:</strong><br>
Assigned to this post: <?= count($results['assigned_levels']) ?>
<?php if (count($results['assigned_levels']) > 0): ?>
(<?= implode(', ', array_slice($results['assigned_levels'], 0, 3)) ?><?= count($results['assigned_levels']) > 3 ? '...' : '' ?>)
<?php endif; ?><br>
Total system levels: <?= $results['membership_levels'] ?>
(β‰ˆ<?= round($total_wlm / max(1, $results['membership_levels']), 1) ?> queries per system level)<br>
<?php if (count($results['assigned_levels']) > 0): ?>
<span style="color: #dc3232; font-weight: bold;">
Expected queries: ~<?= count($results['assigned_levels']) * 5 ?>,
Actual queries: <?= $total_wlm ?>
(<?= round($total_wlm / max(1, count($results['assigned_levels']) * 5), 1) ?>x more than expected)
</span>
<?php endif; ?>
</div>
<?php elseif (isset($results['membership_levels'])): ?>
<p><strong>Total Membership Levels:</strong> <?= $results['membership_levels'] ?>
(β‰ˆ<?= round($total_wlm / max(1, $results['membership_levels']), 1) ?> queries per level)</p>
<?php endif; ?>
<?php if (isset($results['debug_info'])): ?>
<details style="margin-top: 15px;">
<summary style="cursor: pointer; font-weight: bold; color: #dc3232;">
πŸ› Developer Debug Info (Click to expand)
</summary>
<div style="margin-top: 10px; max-height: 400px; overflow-y: auto; background: #f8f8f8; padding: 10px; font-family: monospace; font-size: 11px;">
<?php
$debug = $results['debug_info'];
if (!empty($debug['current_content_levels'])): ?>
<strong>Current Content Levels (<?= count($debug['current_content_levels']) ?>):</strong><br>
<?php foreach (array_slice($debug['current_content_levels'], 0, 5) as $level): ?>
ID: <?= $level['content_id'] ?>, Level: <?= esc_html($level['level_id']) ?>, Type: <?= esc_html($level['type']) ?><br>
<?php endforeach; ?>
<?php if (count($debug['current_content_levels']) > 5): ?>
... and <?= count($debug['current_content_levels']) - 5 ?> more<br>
<?php endif; ?>
<br>
<?php endif; ?>
<?php if (!empty($debug['inherit_levels'])): ?>
<strong style="color: #dc3232;">INHERIT LEVELS DETECTED (<?= count($debug['inherit_levels']) ?>):</strong><br>
<?php foreach ($debug['inherit_levels'] as $level): ?>
ID: <?= $level['content_id'] ?>, Level: <?= esc_html($level['level_id']) ?>, Type: <?= esc_html($level['type']) ?><br>
<?php endforeach; ?>
<br>
<?php endif; ?>
<?php if (isset($debug['child_posts_count']) && $debug['child_posts_count'] > 0): ?>
<strong>Child Posts: <?= $debug['child_posts_count'] ?></strong><br>
Child IDs: <?= implode(', ', array_slice($debug['child_post_ids'], 0, 10)) ?>
<?php if (count($debug['child_post_ids']) > 10): ?>... and <?= count($debug['child_post_ids']) - 10 ?> more<?php endif; ?><br><br>
<?php endif; ?>
<?php if (!empty($debug['content_scheduling'])): ?>
<strong>Content Scheduling (<?= count($debug['content_scheduling']) ?>):</strong><br>
<?php foreach (array_slice($debug['content_scheduling'], 0, 3) as $sched): ?>
Post: <?= $sched['post_id'] ?>, Level: <?= esc_html($sched['mlevel']) ?><br>
<?php endforeach; ?>
<br>
<?php endif; ?>
<?php if (!empty($debug['wlm_options'])): ?>
<strong style="color: #dc3232;">WLM Options:</strong><br>
<?php foreach ($debug['wlm_options'] as $key => $value): ?>
<?= esc_html($key) ?>: <?= esc_html(is_array($value) ? json_encode($value) : $value) ?><br>
<?php endforeach; ?>
<br>
<?php endif; ?>
<?php if (!empty($debug['active_transients'])): ?>
<strong style="color: #dc3232;">Active WLM Transients:</strong><br>
<?php foreach ($debug['active_transients'] as $key => $value): ?>
<?= esc_html($key) ?>: <?= esc_html(is_array($value) ? json_encode($value) : $value) ?><br>
<?php endforeach; ?>
<br>
<?php endif; ?>
<?php if (!empty($debug['wlm_call_stack'])): ?>
<strong>WLM Call Stack:</strong><br>
<?php foreach ($debug['wlm_call_stack'] as $call): ?>
<?= esc_html($call['function']) ?> (<?= esc_html($call['file']) ?>:<?= esc_html($call['line']) ?>)<br>
<?php endforeach; ?>
<?php endif; ?>
</div>
</details>
<?php endif; ?>
<?php
// Show query samples if any significant activity
$has_samples = false;
foreach ($results['query_samples'] as $category => $samples) {
if (!empty($samples)) {
$has_samples = true;
break;
}
}
if ($has_samples): ?>
<details style="margin-top: 15px;">
<summary style="cursor: pointer; font-weight: bold; color: <?= $status_color ?>;">
πŸ“‹ Query Samples (Click to expand)
</summary>
<div style="margin-top: 10px; max-height: 300px; overflow-y: auto;">
<?php foreach ($results['query_samples'] as $category => $samples): ?>
<?php if (!empty($samples)): ?>
<div style="margin-bottom: 10px;">
<strong><?= ucwords(str_replace('_', ' ', $category)) ?>:</strong>
<?php foreach ($samples as $sample): ?>
<div style="background: #f5f5f5; padding: 5px; margin: 3px 0; font-family: monospace; font-size: 11px; word-break: break-all;">
<?= esc_html($sample) ?>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
<?php endforeach; ?>
</div>
</details>
<?php endif; ?>
<p style="margin-bottom: 0; font-size: 12px; color: #666;">
<em>Saved at <?= $results['timestamp'] ?> | Plugin can be deactivated when not needed</em>
</p>
</div>
<?php
}
}
// Initialize the plugin
new WLM_Performance_Debug();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment