bbadrs_is_pro();
$cleaner_url = admin_url('admin.php?page=' . self::MENU_SLUG);
$how_url = admin_url('admin.php?page=' . self::MENU_SLUG . '-how-it-works');
$box = 'background:#fff;border:1px solid #c3c4c7;padding:18px 22px;margin:14px 0;box-shadow:0 1px 1px rgba(0,0,0,.04);';
echo '
';
echo '
' . esc_html__('Getting Started - AI Data Remover', 'ai-data-remover-seo-cleanup-tool') . '
';
echo '
' . esc_html__('AI Data Remover finds the leftover data-* HTML attributes that AI writing tools, page builders, and copy-paste inject into your content, then lets you bulk-remove them on demand. It only ever edits post_content - never theme files, CSS, JavaScript, options, or meta. Follow the four steps below for your first clean-up.', 'ai-data-remover-seo-cleanup-tool') . '
';
// Step-by-step
echo '
';
echo '
' . esc_html__('Step-by-step first run', 'ai-data-remover-seo-cleanup-tool') . '
';
echo '
';
echo '- ' . esc_html__('Install & activate.', 'ai-data-remover-seo-cleanup-tool') . ' ' . esc_html__('You have already done this - the plugin adds the "AI Data Remover" top-level menu (shield icon) in wp-admin.', 'ai-data-remover-seo-cleanup-tool') . '
';
echo '- ' . esc_html__('Back up first (strongly recommended).', 'ai-data-remover-seo-cleanup-tool') . ' ' . esc_html__('Bulk removal rewrites post_content and there is no built-in undo. Take a database backup, or test on staging, before your first run. If post revisions are enabled you can roll back a single post from its revision history.', 'ai-data-remover-seo-cleanup-tool') . '
';
echo '- ';
printf(
wp_kses(
/* translators: %s = link to the Cleaner page (HTML anchor) */
__('Open the tool. There is no separate settings page - everything happens on %s. Choose the content type (Posts or Pages on Free), then pick a scan mode: "All" strips every data-* attribute, "Specific" targets a single attribute by name (e.g. type "elementor-type" to match only data-elementor-type).', 'ai-data-remover-seo-cleanup-tool'),
array('strong' => array(), 'a' => array('href' => array()))
),
'' . esc_html__('AI Data Remover -> Cleaner', 'ai-data-remover-seo-cleanup-tool') . ''
);
echo '
';
echo '- ' . esc_html__('Scan, review, remove.', 'ai-data-remover-seo-cleanup-tool') . ' ' . esc_html__('Click Scan to list matching items with a per-item match count. Tick the rows you want cleaned and click "Remove Selected". Free removes up to 25 items per run; scan up to 500 items per post type.', 'ai-data-remover-seo-cleanup-tool') . '
';
echo '
';
echo '
';
// You're done / verify
echo '
';
echo '
' . esc_html__("You're done - verify it worked", 'ai-data-remover-seo-cleanup-tool') . '
';
echo '
';
echo '- ' . esc_html__('After removing, click Scan again with the same settings. The cleaned items should now show a lower match count (or drop off the list entirely).', 'ai-data-remover-seo-cleanup-tool') . '
';
echo '- ' . esc_html__('Open a cleaned post/page in the block editor or view its source - the removed data-* attributes should be gone from the markup.', 'ai-data-remover-seo-cleanup-tool') . '
';
echo '- ' . esc_html__('Spot-check the front end. If your theme or builder attached JavaScript to a specific data-* attribute, confirm that behaviour still works - remove only attributes you know are inert leftovers.', 'ai-data-remover-seo-cleanup-tool') . '
';
echo '
';
echo '
';
if (!$is_pro) {
echo '
';
echo '
' . esc_html__('Working past the Free limits', 'ai-data-remover-seo-cleanup-tool') . '
';
echo '
' . esc_html__('Free is capped at Posts & Pages, a 500-item scan window, 25 removals per run, and 50 rows per page. Pro removes those caps and adds a Select-All checkbox, WooCommerce products + public custom post types, CSV export of scan results, and hides the upgrade notice - all gated on a valid BBA license.', 'ai-data-remover-seo-cleanup-tool') . '
';
echo '
' . esc_html__('See Pro', 'ai-data-remover-seo-cleanup-tool') . '
';
echo '
';
}
echo '
';
echo '' . esc_html__('Open the Cleaner ->', 'ai-data-remover-seo-cleanup-tool') . ' ';
echo '' . esc_html__('How It Works ->', 'ai-data-remover-seo-cleanup-tool') . '';
echo '
';
echo '
';
}
/**
* How It Works - per-feature explanation (what it does / how to use / Pro).
*/
public function bbadrs_render_how_it_works_page() {
if (!current_user_can(self::CAP)) {
return;
}
$is_pro = $this->bbadrs_is_pro();
$box = 'background:#fff;border:1px solid #c3c4c7;padding:18px 22px;margin:14px 0;box-shadow:0 1px 1px rgba(0,0,0,.04);';
echo '';
echo '
' . esc_html__('How It Works - AI Data Remover', 'ai-data-remover-seo-cleanup-tool') . '
';
echo '
' . esc_html__('What each feature does, how to use it, and where Pro extends it. AI Data Remover changes only post_content - themes, plugins, CSS, JavaScript, options, and meta are never touched.', 'ai-data-remover-seo-cleanup-tool') . '
';
$features = array(
array(
__('Scan Posts & Pages', 'ai-data-remover-seo-cleanup-tool'),
__('Queries your database for any post or page whose content contains "data-" and lists each match with a per-item match count. Results are cached for 10 minutes so the admin page stays fast; the cache clears automatically after a removal.', 'ai-data-remover-seo-cleanup-tool'),
__('Choose Posts or Pages, then click Scan. Read the match count column to see which items carry the most leftover attributes.', 'ai-data-remover-seo-cleanup-tool'),
),
array(
__('Targeted or Universal mode', 'ai-data-remover-seo-cleanup-tool'),
__('"All" mode strips every data-* attribute in one pass. "Specific" mode targets a single named attribute. Custom input is sanitised to letters, digits, and hyphens, so you can never match outside the data-* namespace.', 'ai-data-remover-seo-cleanup-tool'),
__('Pick "Specific" and type an attribute name without the data- prefix (e.g. "id", "elementor-type") to clean just that one; leave it on "All" for a full sweep.', 'ai-data-remover-seo-cleanup-tool'),
),
array(
__('Manual bulk removal', 'ai-data-remover-seo-cleanup-tool'),
__('Nothing is ever removed automatically. You scan, review, tick rows, and submit. The remove action requires a valid WordPress nonce and the manage_options capability, then rewrites only the matched attributes out of post_content.', 'ai-data-remover-seo-cleanup-tool'),
__('Tick the rows you want, click "Remove Selected", and re-scan to confirm. Free removes up to 25 items per run.', 'ai-data-remover-seo-cleanup-tool'),
),
array(
__('Free limits', 'ai-data-remover-seo-cleanup-tool'),
__('The Free tier enforces three hard caps to stay safe and quick on shared hosting: a 500-item scan window per post type, 25 removals per submitted run, and up to 50 rows per page.', 'ai-data-remover-seo-cleanup-tool'),
__('Work in batches: scan, remove 25, re-scan, repeat. Pro removes all three caps.', 'ai-data-remover-seo-cleanup-tool'),
),
array(
__('No data leaves your site', 'ai-data-remover-seo-cleanup-tool'),
__('The Free plugin makes zero external HTTP requests - no calls to bigbad.agency, WordPress.org, or any analytics endpoint. No telemetry, no phone-home. Your content stays in your own database.', 'ai-data-remover-seo-cleanup-tool'),
__('Nothing to configure - this is always on.', 'ai-data-remover-seo-cleanup-tool'),
),
);
foreach ($features as $f) {
echo '
';
echo '
' . esc_html($f[0]) . '
';
echo '
' . esc_html__('What it does:', 'ai-data-remover-seo-cleanup-tool') . ' ' . esc_html($f[1]) . '
';
echo '
' . esc_html__('How to use it:', 'ai-data-remover-seo-cleanup-tool') . ' ' . esc_html($f[2]) . '
';
echo '
';
}
// Pro features (honest)
echo '
';
echo '
' . esc_html__('What Pro adds', 'ai-data-remover-seo-cleanup-tool');
echo ' PRO
';
echo '
' . esc_html__('Every Pro behaviour below is gated on a valid BBA license - none of it unlocks on plugin activation alone.', 'ai-data-remover-seo-cleanup-tool') . '
';
echo '
';
echo '- ' . esc_html__('Additional post types:', 'ai-data-remover-seo-cleanup-tool') . ' ' . esc_html__('scan WooCommerce products and any public custom post type, not just Posts and Pages.', 'ai-data-remover-seo-cleanup-tool') . '
';
echo '- ' . esc_html__('CSV export of scan results:', 'ai-data-remover-seo-cleanup-tool') . ' ' . esc_html__('download every matching item (ID, title, type, status, match count, edit URL) for auditing before you clean.', 'ai-data-remover-seo-cleanup-tool') . '
';
echo '- ' . esc_html__('Master Select-All checkbox:', 'ai-data-remover-seo-cleanup-tool') . ' ' . esc_html__('tick every visible row at once.', 'ai-data-remover-seo-cleanup-tool') . '
';
echo '- ' . esc_html__('Unlimited scan window & bulk removal:', 'ai-data-remover-seo-cleanup-tool') . ' ' . esc_html__('no 500-item scan cap and no 25-item per-run removal cap.', 'ai-data-remover-seo-cleanup-tool') . '
';
echo '- ' . esc_html__('Up to 200 rows per page:', 'ai-data-remover-seo-cleanup-tool') . ' ' . esc_html__('review large scans without paging as often.', 'ai-data-remover-seo-cleanup-tool') . '
';
echo '- ' . esc_html__('Hides the upgrade notice.', 'ai-data-remover-seo-cleanup-tool') . '
';
echo '
';
if (!$is_pro) {
echo '
' . esc_html__('See Pro pricing', 'ai-data-remover-seo-cleanup-tool') . '
';
} else {
echo '
' . esc_html__('Pro is active on this site.', 'ai-data-remover-seo-cleanup-tool') . '
';
}
echo '
';
echo '
';
}
public function bbadrs_render_pro_notice() {
if ($this->bbadrs_is_pro()) {
return;
}
$page = '';
if (isset($_GET['page'])) {
$page = sanitize_key(wp_unslash($_GET['page']));
}
if ($page !== self::MENU_SLUG) {
return;
}
echo "";
echo '' . esc_html__('AI Data Remover (Free)', 'ai-data-remover-seo-cleanup-tool') . ' ';
echo esc_html__('is limited to Posts & Pages, 500 items, and 25 removals per run.', 'ai-data-remover-seo-cleanup-tool') . ' ';
echo esc_html__('Upgrade to PRO to unlock WooCommerce, Select-All, CSV export, and unlimited cleaning.', 'ai-data-remover-seo-cleanup-tool');
echo "
";
}
public function bbadrs_admin_enqueue($hook_suffix) {
// Only load on this plugin's admin page.
if ($hook_suffix !== 'toplevel_page_' . self::MENU_SLUG) {
return;
}
$asset_url = plugin_dir_url(__FILE__) . 'assets/';
wp_register_script(
'bbadrs-ai-drm-admin',
$asset_url . 'admin.js',
array(),
self::VERSION,
true
);
// Optional PRO flag for UI behavior
wp_add_inline_script(
'bbadrs-ai-drm-admin',
'window.BBADRS_AI_DRM = ' . wp_json_encode(array(
'isPro' => $this->bbadrs_is_pro() ? 1 : 0,
)) . ';',
'before'
);
wp_enqueue_script('bbadrs-ai-drm-admin');
wp_register_style(
'bbadrs-ai-drm-admin',
$asset_url . 'admin.css',
array(),
self::VERSION
);
wp_enqueue_style('bbadrs-ai-drm-admin');
}
private function bbadrs_build_regex($mode, $q) {
$mode = ($mode === 'custom') ? 'custom' : 'all';
if ($mode === 'custom' && $q) {
$q = str_replace('data-', '', (string) $q);
$q = preg_replace('/[^a-z0-9\-]/i', '', $q);
return '/\sdata-' . preg_quote($q, '/') . '="[^"]*"/';
}
return '/\sdata-[a-zA-Z0-9\-]+="[^"]*"/';
}
private function bbadrs_clean_content($content, $regex) {
return preg_replace($regex, '', (string) $content);
}
/**
* Cached ID scan to avoid repeated direct queries on every page load.
*/
private function bbadrs_get_candidate_ids($post_type) {
global $wpdb;
$post_type = (string) $post_type;
$cache_key = 'bbadrs_ai_drm_ids_' . $post_type;
$cached = wp_cache_get($cache_key, self::CACHE_GROUP);
if ($cached !== false && is_array($cached)) {
return $cached;
}
// Direct query is acceptable here; we mitigate with caching.
$ids = $wpdb->get_col(
$wpdb->prepare(
"SELECT ID FROM {$wpdb->posts}
WHERE post_type=%s
AND post_status IN('publish','draft','private')
AND post_content LIKE %s
ORDER BY post_modified DESC",
$post_type,
'%data-%'
)
);
if (!is_array($ids)) {
$ids = array();
}
// Cache for 10 minutes (keeps admin snappy while still updating regularly)
wp_cache_set($cache_key, $ids, self::CACHE_GROUP, 10 * MINUTE_IN_SECONDS);
return $ids;
}
public function bbadrs_render_admin_page() {
if (!current_user_can(self::CAP)) {
return;
}
$is_pro = $this->bbadrs_is_pro();
$types = $this->bbadrs_allowed_post_types();
// Nonce for admin screen interactions (GET + POST)
$screen_nonce = wp_create_nonce(self::NONCE_ACTION);
// Sanitize GET inputs
$post_type = isset($types[0]) ? $types[0] : 'post';
if (isset($_GET['post_type'])) {
$post_type = sanitize_key(wp_unslash($_GET['post_type']));
}
if (!in_array($post_type, $types, true)) {
$post_type = isset($types[0]) ? $types[0] : 'post';
}
$pattern = 'all';
if (isset($_GET['pattern'])) {
$pattern = sanitize_key(wp_unslash($_GET['pattern']));
}
$pattern = ($pattern === 'custom') ? 'custom' : 'all';
$q = '';
if (isset($_GET['q'])) {
$q = sanitize_text_field(wp_unslash($_GET['q']));
}
// Optional nonce verification for GET-driven scanning.
if (isset($_GET['_bbadrs_nonce'])) {
$maybe_nonce = sanitize_text_field(wp_unslash($_GET['_bbadrs_nonce']));
if (!wp_verify_nonce($maybe_nonce, self::NONCE_ACTION)) {
echo "