{"id":322338,"date":"2026-06-08T07:03:47","date_gmt":"2026-06-08T07:03:47","guid":{"rendered":"https:\/\/wordpress.org\/plugins\/conflict-detective\/"},"modified":"2026-10-20T08:37:24","modified_gmt":"2026-10-20T08:37:24","slug":"tahhan-conflict-detective","status":"publish","type":"plugin","link":"https:\/\/pl.wordpress.org\/plugins\/tahhan-conflict-detective\/","author":23075774,"comment_status":"closed","ping_status":"closed","template":"","meta":{"version":"2.7.0","stable_tag":"2.7.0","tested":"7.0.7","requires":"5.8","requires_php":"7.4","requires_plugins":null,"header_name":"Tahhan Conflict Detective","header_author":"Tahhan","header_description":"Automatically detects which plugin, theme, or update broke your WordPress site \u2014 without manual trial and error.","assets_banners_color":"12203d","last_updated":"2026-10-20 08:37:24","external_support_url":"","external_repository_url":"","donate_link":"","header_plugin_uri":"https:\/\/github.com\/Tahhan-nl\/Tahhan-Conflict-Detective","header_author_uri":"https:\/\/github.com\/Tahhan-nl","rating":0,"author_block_rating":0,"active_installs":0,"downloads":665,"num_ratings":0,"support_threads":0,"support_threads_resolved":0,"author_block_count":0,"sections":["description","installation","faq","changelog"],"tags":{"2.5.0":{"tag":"2.5.0","author":"mustafatahhan","date":"2026-06-08 07:03:30","revision":3564202},"2.5.1":{"tag":"2.5.1","author":"mustafatahhan","date":"2026-06-11 07:41:03","revision":3568445},"2.5.2":{"tag":"2.5.2","author":"mustafatahhan","date":"2026-06-11 12:05:42","revision":3568797},"2.5.3":{"tag":"2.5.3","author":"mustafatahhan","date":"2026-06-15 09:01:34","revision":3572694},"2.6.0":{"tag":"2.6.0","author":"mustafatahhan","date":"2026-06-16 09:51:08","revision":3574302},"2.6.1":{"tag":"2.6.1","author":"mustafatahhan","date":"2026-06-16 14:19:04","revision":3574621},"2.6.2":{"tag":"2.6.2","author":"mustafatahhan","date":"2026-07-13 12:09:08","revision":3606008},"2.6.3":{"tag":"2.6.3","author":"mustafatahhan","date":"2026-08-04 07:19:44","revision":3633871},"2.6.4":{"tag":"2.6.4","author":"mustafatahhan","date":"2026-08-26 12:08:32","revision":3666945},"2.6.5":{"tag":"2.6.5","author":"mustafatahhan","date":"2026-09-14 13:03:48","revision":3695274},"2.6.6":{"tag":"2.6.6","author":"mustafatahhan","date":"2026-10-14 09:25:42","revision":3745851},"2.7.0":{"tag":"2.7.0","author":"mustafatahhan","date":"2026-10-20 08:37:24","revision":3756173}},"upgrade_notice":{"2.7.0":"<p>Agency Edition: multi-site monitoring, read-only API keys and email alerts. All new features are off until you enable them.<\/p>","2.1.1":"<p>Security and quality patch. Upgrade recommended for all users on 2.1.0.<\/p>","2.0.0":"<p>Major feature release: Conflict Scanner, Safe Testing Mode, and Conflict Wizard.<\/p>"},"ratings":[],"assets_icons":{"icon-128x128.png":{"filename":"icon-128x128.png","revision":3572689,"resolution":"128x128","location":"assets","locale":"","width":128,"height":128},"icon-256x256.png":{"filename":"icon-256x256.png","revision":3572689,"resolution":"256x256","location":"assets","locale":"","width":256,"height":256}},"assets_banners":{"banner-1544x500.png":{"filename":"banner-1544x500.png","revision":3574302,"resolution":"1544x500","location":"assets","locale":"","width":1544,"height":500},"banner-772x250.png":{"filename":"banner-772x250.png","revision":3574302,"resolution":"772x250","location":"assets","locale":"","width":772,"height":250}},"assets_blueprints":{},"all_blocks":[],"tagged_versions":["2.5.0","2.5.1","2.5.2","2.5.3","2.6.0","2.6.1","2.6.2","2.6.3","2.6.4","2.6.5","2.6.6","2.7.0"],"block_files":[],"assets_screenshots":{"screenshot-1.png":{"filename":"screenshot-1.png","revision":3572694,"resolution":"1","location":"assets","locale":"","width":1280,"height":800},"screenshot-2.png":{"filename":"screenshot-2.png","revision":3572694,"resolution":"2","location":"assets","locale":"","width":1280,"height":800},"screenshot-3.png":{"filename":"screenshot-3.png","revision":3572694,"resolution":"3","location":"assets","locale":"","width":1280,"height":800},"screenshot-4.png":{"filename":"screenshot-4.png","revision":3572694,"resolution":"4","location":"assets","locale":"","width":1280,"height":800},"screenshot-5.png":{"filename":"screenshot-5.png","revision":3572694,"resolution":"5","location":"assets","locale":"","width":1280,"height":800},"screenshot-6.png":{"filename":"screenshot-6.png","revision":3572694,"resolution":"6","location":"assets","locale":"","width":1280,"height":800},"screenshot-7.png":{"filename":"screenshot-7.png","revision":3572694,"resolution":"7","location":"assets","locale":"","width":1280,"height":800}},"screenshots":{"1":"Dashboard \u2014 live system overview: active plugins, stat cards, recent changes and the likely conflict culprit widget.","2":"Conflict Scanner \u2014 detected conflicts ranked by confidence score with one-click \"Mark Resolved\".","3":"Safe Testing Mode \u2014 amber active banner shows disabled-plugin count; toggle any plugin on\/off for your session only.","4":"Conflict Wizard \u2014 step-by-step diagnosis selects your symptom and produces a tailored action plan.","5":"Error Log Viewer \u2014 PHP errors parsed from debug.log and server error_log, attributed to the owning plugin with type filters.","6":"Change History \u2014 full audit trail of every plugin activation, deactivation, update, installation and deletion with timestamps.","7":"Health Scan \u2014 on-demand scan for duplicate functionality, outdated plugins, incompatibilities and server configuration issues."}},"plugin_section":[],"plugin_tags":[191246,94,93,1508,2391],"plugin_category":[59],"plugin_contributors":[266135],"plugin_business_model":[],"class_list":["post-322338","plugin","type-plugin","status-publish","hentry","plugin_tags-conflict","plugin_tags-debug","plugin_tags-errors","plugin_tags-health","plugin_tags-plugins","plugin_category-utilities-and-tools","plugin_contributors-mustafatahhan","plugin_committers-mustafatahhan"],"banners":{"banner":"https:\/\/ps.w.org\/tahhan-conflict-detective\/assets\/banner-772x250.png?rev=3574302","banner_2x":"https:\/\/ps.w.org\/tahhan-conflict-detective\/assets\/banner-1544x500.png?rev=3574302","banner_rtl":false,"banner_2x_rtl":false},"icons":{"svg":false,"icon":"https:\/\/ps.w.org\/tahhan-conflict-detective\/assets\/icon-128x128.png?rev=3572689","icon_2x":"https:\/\/ps.w.org\/tahhan-conflict-detective\/assets\/icon-256x256.png?rev=3572689","generated":false},"screenshots":[{"src":"https:\/\/ps.w.org\/tahhan-conflict-detective\/assets\/screenshot-1.png?rev=3572694","caption":"Dashboard \u2014 live system overview: active plugins, stat cards, recent changes and the likely conflict culprit widget."},{"src":"https:\/\/ps.w.org\/tahhan-conflict-detective\/assets\/screenshot-2.png?rev=3572694","caption":"Conflict Scanner \u2014 detected conflicts ranked by confidence score with one-click \"Mark Resolved\"."},{"src":"https:\/\/ps.w.org\/tahhan-conflict-detective\/assets\/screenshot-3.png?rev=3572694","caption":"Safe Testing Mode \u2014 amber active banner shows disabled-plugin count; toggle any plugin on\/off for your session only."},{"src":"https:\/\/ps.w.org\/tahhan-conflict-detective\/assets\/screenshot-4.png?rev=3572694","caption":"Conflict Wizard \u2014 step-by-step diagnosis selects your symptom and produces a tailored action plan."},{"src":"https:\/\/ps.w.org\/tahhan-conflict-detective\/assets\/screenshot-5.png?rev=3572694","caption":"Error Log Viewer \u2014 PHP errors parsed from debug.log and server error_log, attributed to the owning plugin with type filters."},{"src":"https:\/\/ps.w.org\/tahhan-conflict-detective\/assets\/screenshot-6.png?rev=3572694","caption":"Change History \u2014 full audit trail of every plugin activation, deactivation, update, installation and deletion with timestamps."},{"src":"https:\/\/ps.w.org\/tahhan-conflict-detective\/assets\/screenshot-7.png?rev=3572694","caption":"Health Scan \u2014 on-demand scan for duplicate functionality, outdated plugins, incompatibilities and server configuration issues."}],"raw_content":"<!--section=description-->\n<p><strong>\"Which plugin just broke my site?\"<\/strong> \u2014 answered automatically.<\/p>\n\n<p>Existing tools show you <em>that<\/em> an error occurred. Conflict Detective tells you <em>which plugin caused it<\/em> by correlating the plugin change timeline with the error log \u2014 and gives you a confidence score.<\/p>\n\n<h4>Features<\/h4>\n\n<p><strong>Dashboard<\/strong>\nLive overview of your WordPress\/PHP version, active plugins, active theme, memory limit, debug mode, recent changes, and latest errors \u2014 all on one screen.<\/p>\n\n<p><strong>Error Log Viewer<\/strong>\nReads <code>debug.log<\/code> and the server PHP error log automatically. Each entry is attributed to the plugin that owns the file where the error occurred. Filter by Fatal \/ Warning \/ Notice \/ Deprecated.<\/p>\n\n<p><strong>Change History<\/strong>\nFull audit trail of every plugin activation, deactivation, update, and deletion \u2014 with exact timestamps and version diffs (e.g. <code>8.3.0 \u2192 8.4.0<\/code>).<\/p>\n\n<p><strong>Health Scan<\/strong>\nOn-demand scan that detects:<\/p>\n\n<ul>\n<li>Duplicate plugin functionality (multiple SEO, caching, security, or backup plugins)<\/li>\n<li>Known incompatibilities between specific plugin pairs<\/li>\n<li>Outdated plugins (not updated in more than 2 years)<\/li>\n<li>Missing theme files or a missing parent theme<\/li>\n<li>PHP version, memory limit, and <code>max_execution_time<\/code> misconfigurations<\/li>\n<li>Pending WordPress core updates<\/li>\n<\/ul>\n\n<p><strong>Conflict Scanner<\/strong>\nAutomatically correlates plugin update timestamps with error spikes and reports a suspect plugin with a confidence percentage. Detected conflicts are stored in the database and can be marked as resolved with one click.<\/p>\n\n<p><strong>Safe Testing Mode<\/strong>\nDisable any plugin for your own admin session while visitors remain completely unaffected. Cookie-isolated \u2014 the live site is never touched.<\/p>\n\n<p><strong>Conflict Wizard<\/strong>\nStep-by-step guided diagnosis. Choose your symptom (white screen, login problem, WooCommerce issue, slow site, broken admin, front-end error, or other) and the wizard automatically analyses recent changes and errors to produce a tailored action plan.<\/p>\n\n<p><strong>Performance Monitor<\/strong> <em>(new in 2.6.0)<\/em>\nTracks the estimated load time, memory delta, and database query count for every active plugin during the current page load. Results are color-coded as Fast \/ Slow \/ Heavy and refreshed on demand.<\/p>\n\n<p><strong>Cron Monitor<\/strong> <em>(new in 2.6.0)<\/em>\nLists all scheduled WordPress Cron events with their next run time and interval. Overdue events are highlighted in red. Each event can be triggered manually via a \"Run Now\" button \u2014 validated against the live WP-Cron schedule before execution.<\/p>\n\n<p><strong>AJAX \/ REST Monitor<\/strong> <em>(new in 2.6.0)<\/em>\nAutomatically logs slow AJAX and REST API calls (those taking more than 500 ms) to a dedicated database table. Filter by All \/ AJAX \/ REST \/ Slow to quickly identify bottleneck endpoints.<\/p>\n\n<p><strong>Plugin Interaction Map<\/strong> <em>(new in 2.6.0)<\/em>\nVisualises your plugins grouped into known ecosystem clusters (WooCommerce, Elementor, Yoast SEO, Jetpack, ACF, Gravity Forms, and more). Highlights declared plugin dependencies and shows active\/inactive state at a glance. Plugins that don't belong to a known ecosystem are listed separately as Standalone Plugins.<\/p>\n\n<h4>Agency Edition (new in 2.7.0)<\/h4>\n\n<p><strong>Multi-site monitoring dashboard<\/strong>\nMonitor up to 50 client sites from one WordPress install. Each row shows the site's status (All clear \/ Warning \/ Critical), open conflicts with the top suspect, fatal errors in the last 24 hours, critical health issues and when it was last checked. Sites are polled automatically every 5, 15 or 60 minutes, or on demand.<\/p>\n\n<p><strong>Read-only API keys<\/strong>\nEach monitored site generates its own key under <strong>Agency \u2192 Remote Access<\/strong>. Keys are shown once, stored only as a SHA-256 hash, can expire after 30\/90\/365 days, and can be rotated or revoked instantly. A key can only read status counts \u2014 it cannot change anything.<\/p>\n\n<p><strong>Email alerts<\/strong>\nAn hourly digest email when something <em>new<\/em> happens: fatal errors, conflicts with 50 %+ confidence, or critical health issues. Choose the severity (critical \/ high \/ everything) and up to 10 recipients. The same conflict or issue is never emailed twice, and switching alerts on does not email the existing backlog.<\/p>\n\n<h3>External services<\/h3>\n\n<p>Conflict Detective does not contact the plugin author or any third-party service. Two optional features, both off by default and configured by an administrator, send data elsewhere:<\/p>\n\n<ul>\n<li><strong>Agency hub<\/strong> \u2014 when you add a remote site under Agency \u2192 Monitored Sites, this site sends an HTTPS GET request to that site's <code>\/wp-json\/tahhan-cd\/v1\/status<\/code> endpoint at the interval you choose. The request contains the remote site's API key (in the <code>X-Tahcd-Key<\/code> header) and the user agent <code>TahhanConflictDetective\/&lt;version&gt;<\/code>. Only sites you add are contacted; their own terms and privacy policies apply.<\/li>\n<li><strong>Email alerts<\/strong> \u2014 when enabled, digest emails are sent through your site's <code>wp_mail()<\/code> configuration to the recipients you enter. They contain error-log excerpts, plugin names and health-scan messages from this site.<\/li>\n<\/ul>\n\n<!--section=installation-->\n<ol>\n<li>Upload the <code>tahhan-conflict-detective<\/code> folder to the <code>\/wp-content\/plugins\/<\/code> directory.<\/li>\n<li>Activate the plugin through the <strong>Plugins<\/strong> menu in WordPress.<\/li>\n<li>Navigate to <strong>Conflict Detective<\/strong> in the WordPress admin sidebar.<\/li>\n<\/ol>\n\n<p>No configuration is required for the diagnostic tools \u2014 all scanning is done on demand inside the admin area. The optional Agency features (remote monitoring, email alerts) are off until you enable them under <strong>Conflict Detective \u2192 Agency<\/strong>.<\/p>\n\n<!--section=faq-->\n<dl>\n<dt id=\"does%20this%20plugin%20slow%20down%20my%20site%3F\"><h3>Does this plugin slow down my site?<\/h3><\/dt>\n<dd><p>No. All analysis runs on demand inside the WordPress admin. The Performance Monitor records a few timestamps while plugins load, and the slow-request monitor only writes to the database when a request takes longer than 500 ms. Agency features run in WP-Cron and only when you enable them.<\/p><\/dd>\n<dt id=\"what%20log%20files%20does%20it%20read%3F\"><h3>What log files does it read?<\/h3><\/dt>\n<dd><p>wp-content\/debug.log and the server PHP <code>error_log<\/code> path returned by <code>ini_get('error_log')<\/code>.<\/p><\/dd>\n<dt id=\"do%20i%20need%20to%20configure%20anything%3F\"><h3>Do I need to configure anything?<\/h3><\/dt>\n<dd><p>No. Optionally, enable <code>WP_DEBUG<\/code> and <code>WP_DEBUG_LOG<\/code> in <code>wp-config.php<\/code> to populate the error log.<\/p><\/dd>\n<dt id=\"does%20safe%20testing%20mode%20affect%20my%20visitors%3F\"><h3>Does Safe Testing Mode affect my visitors?<\/h3><\/dt>\n<dd><p>Never. Safe Testing Mode uses a session cookie that is only present for your admin session. Visitors always see the live site.<\/p><\/dd>\n<dt id=\"what%20happens%20if%20i%20install%20the%20plugin%20via%20ftp%3F\"><h3>What happens if I install the plugin via FTP?<\/h3><\/dt>\n<dd><p>The plugin detects missing database tables on every request and recreates them automatically \u2014 no activation hook required.<\/p><\/dd>\n<dt id=\"is%20this%20plugin%20compatible%20with%20multisite%3F\"><h3>Is this plugin compatible with Multisite?<\/h3><\/dt>\n<dd><p>Yes, per site. Because the PHP error log is shared by every site on a network, the Error Log, email alerts and the Agency hub are only available to network (super) administrators. Site administrators keep access to the other tools.<\/p><\/dd>\n<dt id=\"how%20does%20the%20performance%20monitor%20work%3F\"><h3>How does the Performance Monitor work?<\/h3><\/dt>\n<dd><p>Performance Monitor hooks into WordPress's <code>plugin_loaded<\/code> action, which fires once for every plugin that loads. It records a snapshot of the PHP execution time, memory usage, and database query count immediately before and after each plugin loads, then computes the delta. The results are stored in a 5-minute transient and can be refreshed on demand. Because measurements happen at request time, values vary between requests \u2014 treat them as estimates rather than precise benchmarks.<\/p><\/dd>\n<dt id=\"does%20the%20ajax%20%2F%20rest%20monitor%20log%20every%20request%3F\"><h3>Does the AJAX \/ REST Monitor log every request?<\/h3><\/dt>\n<dd><p>No. Only requests that take longer than 500 ms are written to the database. The log is automatically trimmed to the most recent 500 entries.<\/p><\/dd>\n<dt id=\"can%20i%20manually%20trigger%20a%20wordpress%20cron%20event%3F\"><h3>Can I manually trigger a WordPress Cron event?<\/h3><\/dt>\n<dd><p>Yes. Go to <strong>Conflict Detective \u2192 Cron Monitor<\/strong> and click the \"Run Now\" button next to any event. The plugin validates the hook name against the live WP-Cron schedule before execution so arbitrary code cannot be triggered via this interface.<\/p><\/dd>\n<dt id=\"what%20is%20the%20plugin%20interaction%20map%3F\"><h3>What is the Plugin Interaction Map?<\/h3><\/dt>\n<dd><p>The Interaction Map groups your installed plugins into known ecosystem clusters (WooCommerce, Elementor, Yoast SEO, Jetpack, ACF, Gravity Forms, Contact Form 7, WPML, LearnDash, Divi, WP Rocket, and more). Plugins within the same ecosystem are more likely to interact and potentially conflict with each other. The map also reads the <code>Requires Plugins<\/code> header introduced in WordPress 6.5 to show explicit declared dependencies.<\/p><\/dd>\n<dt id=\"what%20does%20the%20remote%20status%20endpoint%20expose%3F\"><h3>What does the remote status endpoint expose?<\/h3><\/dt>\n<dd><p>GET \/wp-json\/tahhan-cd\/v1\/status returns counts only: number of open conflicts and the top suspect's plugin name and confidence, total and last-24h fatal errors, health-scan issue counts, the last plugin change, the site name, and the WordPress, PHP and Conflict Detective versions. No file paths, log messages or personal data. It only answers requests that send a valid key in the <code>X-Tahcd-Key<\/code> header \u2014 never in the URL. It is disabled until you generate a key.<\/p><\/dd>\n<dt id=\"how%20are%20api%20keys%20protected%3F\"><h3>How are API keys protected?<\/h3><\/dt>\n<dd><p>On the monitored site only a SHA-256 hash of the key is stored. On the agency site, keys of remote sites are encrypted with libsodium using a key derived from your <code>AUTH_KEY<\/code> salt \u2014 if you change your salts, re-add the remote sites. After 10 failed attempts in 15 minutes an IP is throttled; a request with a valid key is always accepted.<\/p><\/dd>\n<dt id=\"why%20is%20https%20required%20for%20remote%20sites%3F\"><h3>Why is HTTPS required for remote sites?<\/h3><\/dt>\n<dd><p>The API key travels in a request header. Remote URLs must use HTTPS on port 443; local, private-network, link-local and reserved addresses are refused to prevent server-side request forgery. Developers can allow plain HTTP on a test network with the <code>tahcd_allow_insecure_remote<\/code> filter. Known limitations: remote sites must have an IPv4 (A) record, and monitoring is disabled while an outbound HTTP proxy (<code>WP_PROXY_HOST<\/code>) is configured, because the destination could then not be verified.<\/p><\/dd>\n<dt id=\"my%20site%20is%20behind%20cloudflare%20or%20a%20reverse%20proxy.%20does%20throttling%20still%20work%3F\"><h3>My site is behind Cloudflare or a reverse proxy. Does throttling still work?<\/h3><\/dt>\n<dd><p>Yes. A valid key is never throttled. If you want throttling to use the real visitor IP, map it with the <code>tahcd_rest_client_ip<\/code> filter from a header your proxy sets.<\/p><\/dd>\n<dt id=\"do%20email%20alerts%20need%20a%20real%20cron%20job%3F\"><h3>Do email alerts need a real cron job?<\/h3><\/dt>\n<dd><p>Alerts and remote polling run through WP-Cron, which runs when your site receives traffic. On low-traffic sites, configure a system cron that calls <code>wp-cron.php<\/code> for timely alerts.<\/p><\/dd>\n\n<\/dl>\n\n<!--section=changelog-->\n<h4>2.7.0<\/h4>\n\n<ul>\n<li>New: Agency hub \u2014 monitor up to 50 Conflict Detective sites from one dashboard, polled every 5\/15\/60 minutes or on demand.<\/li>\n<li>New: Read-only REST endpoint <code>GET \/wp-json\/tahhan-cd\/v1\/status<\/code>, protected by a per-site API key (hash-only storage, expiry, rotation, revocation, per-IP throttling of failed attempts).<\/li>\n<li>New: Email alerts \u2014 hourly digest of new fatal errors, conflicts and critical health issues, with severity levels, deduplication and a test button.<\/li>\n<li>Security: Outbound requests resolve every A\/AAAA record, refuse private\/link-local\/reserved\/CGNAT addresses, pin the connection to the vetted IP, allow HTTPS on port 443 only and follow no redirects.<\/li>\n<li>Security: Remote sites' API keys are encrypted at rest with libsodium.<\/li>\n<li>Security: On multisite the shared error log (Error Log tab, dashboard card, Conflict Wizard timeline), email alerts and agency hub are restricted to network administrators.<\/li>\n<li>Fix: The activation hook was registered too late (inside <code>plugins_loaded<\/code>) and never ran on activation; tables were only created on the next request.<\/li>\n<li>Fix: Error logs larger than 50 MB were skipped entirely; the newest entries are now always read (memory is bounded at 2 MB).<\/li>\n<li>Uninstall now also removes the new options, cron events and transients on every site.<\/li>\n<\/ul>\n\n<h4>2.6.6<\/h4>\n\n<ul>\n<li>Fix: <code>Error_Log::tail()<\/code> \u2014 <code>fread()<\/code> return now checked; read errors no longer inject the word \"false\" into parsed log entries.<\/li>\n<li>Fix: <code>Conflict_Scanner::get_recent_changes()<\/code> \u2014 <code>$wpdb-&gt;get_results()<\/code> null return now guarded; prevents TypeError on DB failure.<\/li>\n<li>Fix: <code>Dashboard::render_dashboard()<\/code> \u2014 <code>strtotime()<\/code> false guard on culprit banner timestamp; corrupt dates no longer show \"01-01-1970\".<\/li>\n<li>Fix: <code>Dashboard::render_scanner()<\/code> \u2014 same false guard applied to suspect card timestamps.<\/li>\n<li>Performance: <code>Dashboard::render_dashboard()<\/code> \u2014 eliminated one redundant log-file parse and one extra DB query; both data sources now fetched once.<\/li>\n<\/ul>\n\n<h4>2.6.5<\/h4>\n\n<ul>\n<li>Fix: <code>Safe_Mode::ajax_toggle_plugin()<\/code> \u2014 hardcoded 'Forbidden' string now translatable via <code>__()<\/code>.<\/li>\n<li>Fix: <code>Dashboard::ajax_clear_log()<\/code> \u2014 missing <code>return<\/code> after error response when log file does not exist.<\/li>\n<li>Fix: <code>Dashboard::ajax_clear_log()<\/code> \u2014 missing <code>return<\/code> after error response when <code>put_contents()<\/code> fails.<\/li>\n<li>Fix: <code>Cron_Monitor::ajax_run_cron_event()<\/code> \u2014 missing <code>return<\/code> after error response for unknown-hook validation; <code>run_event()<\/code> would execute despite the error already being sent.<\/li>\n<\/ul>\n\n<h4>2.6.4<\/h4>\n\n<ul>\n<li>Fix: <code>cd_ajax_log.created_at<\/code> schema now includes <code>DEFAULT CURRENT_TIMESTAMP<\/code> \u2014 prevents INSERT failure under MySQL strict mode.<\/li>\n<li>Fix: <code>cd_conflicts.changed_at<\/code> schema changed to <code>DEFAULT NULL<\/code> \u2014 prevents INSERT failure if the column is omitted in strict mode.<\/li>\n<li>Fix: <code>drop_tables()<\/code> now also deletes the <code>tahcd_main_table_exists<\/code> transient on uninstall \u2014 no orphaned wp_options row.<\/li>\n<li>Fix: <code>tables_exist()<\/code> now checks both <code>cd_plugin_changes<\/code> (v1) and <code>cd_ajax_log<\/code> (v3) \u2014 a partial install is correctly detected.<\/li>\n<li>Fix: <code>Health_Scan::run()<\/code> checks <code>$wpdb-&gt;insert()<\/code> return value and sets <code>db_error<\/code> flag in result on failure.<\/li>\n<li>Fix: <code>Health_Scan::run()<\/code> <code>scanned_at<\/code> in the returned array now uses the same UTC timestamp stored in the database \u2014 was returning local time while the DB row held UTC.<\/li>\n<li>Fix: <code>scan_theme()<\/code> guards <code>get_site_transient('update_themes')<\/code> with <code>is_object()<\/code> before property access.<\/li>\n<li>Fix: <code>scan_server()<\/code> guards <code>get_site_transient('update_core')<\/code> with <code>is_object()<\/code> before property access.<\/li>\n<li>Fix: <code>Change_History::get_recent()<\/code> returns an empty array on DB error instead of propagating <code>null<\/code> (which violated the declared <code>array<\/code> return type).<\/li>\n<li>Fix: <code>Change_History::on_update()<\/code> option key for previous-version tracking now uses <code>md5()<\/code> instead of <code>sanitize_key()<\/code> \u2014 <code>sanitize_key()<\/code> strips <code>\/<\/code> and <code>.<\/code>, causing path collisions between plugins with similar names.<\/li>\n<li>Fix: <code>Change_History::write()<\/code> now returns <code>bool<\/code> so callers can check whether the DB insert succeeded.<\/li>\n<li>Fix: <code>Change_History::on_update()<\/code> only advances the previous-version baseline (<code>update_option<\/code>) when the audit-log write actually succeeded.<\/li>\n<\/ul>\n\n<h4>2.6.3<\/h4>\n\n<ul>\n<li>Fix: <code>get_site_transient('update_plugins')<\/code> return value guarded with <code>is_object()<\/code> before property access \u2014 prevents PHP 8.x warning on fresh installs.<\/li>\n<li>Fix: <code>WP_Filesystem()<\/code> return value checked before calling <code>put_contents()<\/code> \u2014 prevents fatal on FTP\/SSH filesystem environments.<\/li>\n<li>Fix: AJAX log timestamps stored as UTC (<code>current_time('mysql', true)<\/code>) instead of local WordPress time \u2014 fixes time-correlation bugs on non-UTC sites.<\/li>\n<li>Fix: <code>verify_token()<\/code> result cached with a static variable \u2014 prevents a duplicate DB query per request when Safe Mode is active.<\/li>\n<li>Fix: <code>on_delete<\/code> hook now correctly reads plugin data before deletion (fires on <code>delete_plugin<\/code>, not <code>deleted_plugin<\/code>).<\/li>\n<li>Fix: <code>read_plugin_data()<\/code> validates path stays within <code>WP_PLUGIN_DIR<\/code> via <code>realpath()<\/code> \u2014 path-traversal hardening.<\/li>\n<li>Fix: <code>get_plugins()<\/code> cached with a static variable in <code>attribute_to_plugin()<\/code> \u2014 eliminates N+1 calls when attributing log entries.<\/li>\n<li>Fix: <code>strtotime()<\/code> return value validated in <code>score_plugin()<\/code> \u2014 avoids incorrect confidence scores from invalid timestamps.<\/li>\n<li>Fix: <code>usort<\/code> comparator in Wizard timeline uses <code>&lt;=&gt;<\/code> instead of integer subtraction \u2014 prevents overflow on 32-bit PHP.<\/li>\n<li>Fix: Plugin name in Wizard advice string escaped with <code>esc_html()<\/code> before interpolation.<\/li>\n<li>Fix: <code>str_replace('.php', \u2026)<\/code> in Interaction Map now only removes the trailing extension, not mid-name occurrences.<\/li>\n<li>Fix: Missing <code>return<\/code> after <code>wp_send_json_error()<\/code> added to all AJAX capability-check guards.<\/li>\n<li>Fix: Hard-coded 'Forbidden', 'Invalid plugin.', 'Unknown plugin.' strings now passed through <code>__()<\/code>.<\/li>\n<li>Fix: Cron Monitor <code>display<\/code> key access guarded with <code>isset()<\/code>.<\/li>\n<li>Fix: SHOW TABLES query in <code>maybe_upgrade()<\/code> cached with a 1-hour transient \u2014 eliminates a raw DB query on every page load.<\/li>\n<li>Fix: Multisite uninstall now iterates all sub-sites via <code>switch_to_blog()<\/code> \/ <code>restore_current_blog()<\/code>.<\/li>\n<li>Fix: <code>$ajax_start<\/code> static property reset to <code>null<\/code> after logging to prevent duplicate entries.<\/li>\n<\/ul>\n\n<h4>2.6.2<\/h4>\n\n<ul>\n<li>Security: plugin path in Safe Mode now validated with <code>plugin_basename()<\/code> to prevent path traversal.<\/li>\n<li>Security: AJAX monitor now reads from <code>$_POST<\/code> instead of <code>$_REQUEST<\/code>.<\/li>\n<li>Fix: all early-exit AJAX error responses now include <code>return<\/code> for clarity.<\/li>\n<li>Fix: <code>filemtime()<\/code> calls for CSS\/JS assets now guarded with <code>file_exists()<\/code>.<\/li>\n<li>Fix: Change History table uses null-coalescing guards on DB result properties.<\/li>\n<li>Fix: <code>strtok()<\/code> replaced with stateless <code>explode()<\/code> in Error Log file attribution.<\/li>\n<li>Fix: Health Scan issue count now guards against non-array scan section returns.<\/li>\n<li>Code: Wizard symptom labels moved to a <code>symptoms()<\/code> method so they are translatable.<\/li>\n<li>Code: Hardcoded \"Plugin:\" label in Error Log Viewer is now translatable.<\/li>\n<\/ul>\n\n<h4>2.6.1<\/h4>\n\n<ul>\n<li>Updated: Tested up to WordPress 7.0.<\/li>\n<\/ul>\n\n<h4>2.6.0<\/h4>\n\n<ul>\n<li>New: Performance Monitor tab \u2014 tracks per-plugin load time, memory delta, and DB query count during page load; color-coded Fast \/ Slow \/ Heavy badges; Refresh Data button.<\/li>\n<li>New: Cron Monitor tab \u2014 lists all scheduled WP-Cron events with next run time and overdue detection; \"Run Now\" button per event; overdue count summary badge at top.<\/li>\n<li>New: AJAX \/ REST Monitor tab \u2014 logs slow AJAX and REST API calls (&gt;500 ms) to a dedicated DB table; filter bar for All \/ AJAX \/ REST \/ Slow views.<\/li>\n<li>New: Plugin Interaction Map tab \u2014 visualises plugin dependency clusters (WooCommerce, Elementor, Yoast, Jetpack, ACF, etc.) and standalone plugins with active\/inactive state.<\/li>\n<li>Security: cron \"Run Now\" AJAX handler now validates the hook against the live WP-Cron schedule before execution.<\/li>\n<li>Security: AJAX monitor REST timing now reads from <code>$_SERVER<\/code> directly (was using non-standard <code>$GLOBALS['_SERVER']<\/code>).<\/li>\n<li>Security: AJAX log <code>get_entries()<\/code> now selects only required columns instead of <code>SELECT *<\/code>.<\/li>\n<li>DB: new <code>{prefix}cd_ajax_log<\/code> table (schema version 3).<\/li>\n<\/ul>\n\n<h4>2.5.1<\/h4>\n\n<ul>\n<li>Fixed: uninstall routine now correctly deletes <code>tahcd_prev_version_*<\/code> options (was using the stale <code>pcd_prev_version_<\/code> prefix, leaving orphaned rows in wp_options).<\/li>\n<li>Fixed: Safe Mode plugin list no longer shows Conflict Detective itself (self-exclusion filter used the old slug <code>conflict-detective\/conflict-detective.php<\/code>).<\/li>\n<li>Docs: readme.txt changelog updated with entries for v2.2.0 through v2.5.0.<\/li>\n<\/ul>\n\n<h4>2.5.0<\/h4>\n\n<ul>\n<li>All plugin-specific prefixes standardised to <code>tahcd_<\/code> \/ <code>TAHCD_<\/code> to satisfy WordPress.org uniqueness requirements (constants, AJAX actions, nonce, user meta, cookie, option keys, script handle, JS data object).<\/li>\n<\/ul>\n\n<h4>2.4.0<\/h4>\n\n<ul>\n<li>Plugin renamed to Tahhan Conflict Detective (slug: <code>tahhan-conflict-detective<\/code>) for WordPress.org compliance. Namespace changed to <code>TahhanConflictDetective<\/code>. Text domain changed to <code>tahhan-conflict-detective<\/code>.<\/li>\n<li>Debug log clear handler now uses <code>WP_Filesystem::put_contents()<\/code> instead of <code>file_put_contents()<\/code>.<\/li>\n<\/ul>\n\n<h4>2.3.1<\/h4>\n\n<ul>\n<li>Fixed critical infinite recursion crash: <code>maybe_filter_active_plugins()<\/code> called <code>get_option('active_plugins')<\/code> which re-triggered the same filter, causing a stack overflow. Fixed by reading directly from <code>$wpdb<\/code>.<\/li>\n<\/ul>\n\n<h4>2.3.0<\/h4>\n\n<ul>\n<li>CI action SHA pinning for security hardening. Code cleanup in class-dashboard.php and class-error-log.php.<\/li>\n<\/ul>\n\n<h4>2.2.0<\/h4>\n\n<ul>\n<li>Safe Mode tab fully functional: Start\/Stop button now triggers AJAX and reloads on success.<\/li>\n<li>Architecture fix: <code>Safe_Mode::init()<\/code> and <code>Database::maybe_upgrade()<\/code> moved to file-load time.<\/li>\n<\/ul>\n\n<h4>2.1.4<\/h4>\n\n<ul>\n<li>Fixed critical bug: Safe_Mode::init() and Database::maybe_upgrade() were registered inside Plugin::init() on plugins_loaded priority 5, meaning they were never called (priority 1 and 0 already fired). Both are now wired up directly at file load time, outside the Plugin class.<\/li>\n<li>Safe Mode tab fully redesigned: inactive state shows a card with instructions and a Start button; active state shows an amber banner with plugin count and a Stop button above the plugin toggle list.<\/li>\n<li>JavaScript Safe Mode toggle now uses delegated event binding ($(document).on) so it works regardless of DOM load order, and reloads the page on success to reflect the new state.<\/li>\n<li>Added safeModeLoading and safeModeStop localised strings for button feedback during AJAX calls.<\/li>\n<li>Added CSS for .pcd-safe-mode-banner, .pcd-btn-stop, .pcd-safe-mode-steps, and .pcd-safe-mode-count.<\/li>\n<\/ul>\n\n<h4>2.1.3<\/h4>\n\n<ul>\n<li>Added Safe Mode tab UI (render_safe_mode placeholder).<\/li>\n<\/ul>\n\n<h4>2.1.1<\/h4>\n\n<ul>\n<li>Security: plugin slug from AJAX request now validated against the installed plugins list before being stored in user meta<\/li>\n<li>All JavaScript UI strings are now translatable via wp_localize_script \u2014 no hardcoded English in JS<\/li>\n<li>Replaced last remaining emoji with a Dashicon in the Conflict Wizard<\/li>\n<li>Fixed menu position conflict with WordPress core Plugins menu (changed from 65 to 65.1)<\/li>\n<li>Added languages\/ directory to match the Domain Path declaration in the plugin header<\/li>\n<li>Fixed navigation instructions in documentation (plugin is a top-level menu, not under Tools)<\/li>\n<li>LICENSE updated to full GPL-2.0-or-later text including the \"or any later version\" clause<\/li>\n<\/ul>\n\n<h4>2.0.0<\/h4>\n\n<ul>\n<li>Added Conflict Scanner with confidence percentage<\/li>\n<li>Added Safe Testing Mode (admin-only plugin toggle, visitors unaffected)<\/li>\n<li>Added Conflict Wizard with 7 symptom categories and automatic analysis<\/li>\n<li>New database table for storing scanner results<\/li>\n<\/ul>\n\n<h4>1.0.0<\/h4>\n\n<ul>\n<li>Initial release \u2014 Dashboard, Error Log Viewer, Plugin Change History, Health Scan<\/li>\n<\/ul>","raw_excerpt":"Automatically detects which plugin, theme, or update broke your WordPress site \u2014 without manual trial and error.","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/pl.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin\/322338","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/pl.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin"}],"about":[{"href":"https:\/\/pl.wordpress.org\/plugins\/wp-json\/wp\/v2\/types\/plugin"}],"replies":[{"embeddable":true,"href":"https:\/\/pl.wordpress.org\/plugins\/wp-json\/wp\/v2\/comments?post=322338"}],"author":[{"embeddable":true,"href":"https:\/\/pl.wordpress.org\/plugins\/wp-json\/wporg\/v1\/users\/mustafatahhan"}],"wp:attachment":[{"href":"https:\/\/pl.wordpress.org\/plugins\/wp-json\/wp\/v2\/media?parent=322338"}],"wp:term":[{"taxonomy":"plugin_section","embeddable":true,"href":"https:\/\/pl.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_section?post=322338"},{"taxonomy":"plugin_tags","embeddable":true,"href":"https:\/\/pl.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_tags?post=322338"},{"taxonomy":"plugin_category","embeddable":true,"href":"https:\/\/pl.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_category?post=322338"},{"taxonomy":"plugin_contributors","embeddable":true,"href":"https:\/\/pl.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_contributors?post=322338"},{"taxonomy":"plugin_business_model","embeddable":true,"href":"https:\/\/pl.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_business_model?post=322338"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}