prefix . 'il_pilares') whose names and column lists // are constants under plugin control. Generic WordPress checks // for direct DB queries, caching and prepared-SQL interpolation // don't apply meaningfully here. See docs/LICENSING.md for the // full architecture rationale. // Defence in depth: WordPress already invokes this from a context // that requires manage_options, but we re-check just in case the // uninstaller is called from a custom WP-CLI script or similar. if ( function_exists( 'current_user_can' ) && ! current_user_can( 'activate_plugins' ) ) { return; } // Honour the user's choice: only purge if explicitly enabled. if ( ! get_option( 'flink_delete_data_on_uninstall', 0 ) ) return; global $wpdb; // ── 1. Drop all plugin tables ────────────────────────────────── $flink_tables = array( $wpdb->prefix . 'il_reglas', $wpdb->prefix . 'il_pilares', $wpdb->prefix . 'il_content_templates', $wpdb->prefix . 'il_semantic_links', $wpdb->prefix . 'flink_semantic_embeddings', $wpdb->prefix . 'il_content_queue', $wpdb->prefix . 'il_content_log', $wpdb->prefix . 'il_content_jobs', $wpdb->prefix . 'il_link_stats', $wpdb->prefix . 'il_log', $wpdb->prefix . 'il_exclusions', $wpdb->prefix . 'il_image_templates', $wpdb->prefix . 'il_kb_bases', $wpdb->prefix . 'il_kb_entries', $wpdb->prefix . 'il_pipeline_schedules', $wpdb->prefix . 'il_pipeline_runs', $wpdb->prefix . 'il_translation_jobs', ); foreach ( $flink_tables as $flink_table ) { // Table names are static and built from $wpdb->prefix — safe to interpolate $wpdb->query( "DROP TABLE IF EXISTS {$flink_table}" ); } // ── 2. Delete all plugin options ─────────────────────────────── // The LIKE 'il_%' pattern captures every plugin-prefixed option // in a single sweep — no need to enumerate them. $flink_options = $wpdb->get_col( "SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE 'il\_%' OR option_name LIKE 'ilc\_%'" ); foreach ( $flink_options as $flink_opt ) { delete_option( $flink_opt ); } // ── 3. Delete plugin transients (option_name and sitemeta) ───── $wpdb->query( "DELETE FROM {$wpdb->options} WHERE option_name LIKE '\_transient\_il\_%' OR option_name LIKE '\_transient\_timeout\_il\_%' OR option_name LIKE '\_site\_transient\_il\_%' OR option_name LIKE '\_site\_transient\_timeout\_il\_%'" ); // ── 4. Delete plugin post meta ───────────────────────────────── $wpdb->query( "DELETE FROM {$wpdb->postmeta} WHERE meta_key LIKE '\_il\_%' OR meta_key LIKE 'il\_%'" ); // ── 5. Unschedule cron events ────────────────────────────────── // Plugin-registered hooks. Listed explicitly to avoid removing // any third-party cron with similar names by accident. $flink_cron_hooks = array( 'flink_pipeline_tick', 'flink_semantic_process_batch', 'flink_translation_tick', 'flink_content_process_queue', ); foreach ( $flink_cron_hooks as $flink_hook ) { while ( wp_next_scheduled( $flink_hook ) ) { wp_unschedule_event( wp_next_scheduled( $flink_hook ), $flink_hook ); } wp_clear_scheduled_hook( $flink_hook ); } // ── 6. Remove plugin's upload directory (image templates) ────── // The directory is created at uploads/04-il-templates/ by the // images module (Paid only). It may not exist in Free installs // but the check is cheap and harmless either way. $flink_uploads = wp_upload_dir(); if ( ! empty( $flink_uploads['basedir'] ) ) { $flink_template_dir = trailingslashit( $flink_uploads['basedir'] ) . '04-il-templates'; if ( is_dir( $flink_template_dir ) ) { // Recursive removal, leaf files first. $flink_items = @scandir( $flink_template_dir ); if ( is_array( $flink_items ) ) { foreach ( $flink_items as $flink_item ) { if ( $flink_item === '.' || $flink_item === '..' ) continue; $flink_full = trailingslashit( $flink_template_dir ) . $flink_item; if ( is_file( $flink_full ) ) wp_delete_file( $flink_full ); } } // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_rmdir -- WP_Filesystem requires credentials setup and is overkill for an uninstall cleanup of an empty plugin-owned directory. @rmdir( $flink_template_dir ); } }