BuddyDev

Search

Delete All Notifications Tool

  • Participant
    Level: Guru
    Posts: 910
    Tosin on #55583

    Hello Brajesh

    Ill like to share this plugin with you, can you review it when you are free

    ` <?php
    /**
    * Plugin Name: BuddyPress Delete All Notifications Tool
    * Description: Allows site administrators to delete all BuddyPress notifications for all users.
    * Version: 1.1
    * License: GPLv2 or later
    * Requires at least: 5.6
    * Requires PHP: 7.4
    * Requires Plugins: buddypress
    */

    defined( ‘ABSPATH’ ) || exit;

    class BP_Delete_All_Notifications {

    public function __construct() {
    add_action( ‘admin_menu’, array( $this, ‘add_admin_menu’ ) );
    add_action( ‘admin_post_delete_all_bp_notifications’, array( $this, ‘handle_delete_notifications’ ) );
    add_action( ‘admin_notices’, array( $this, ‘admin_notices’ ) );
    add_action( ‘admin_enqueue_scripts’, array( $this, ‘enqueue_confirmation_script’ ) );
    }

    public function add_admin_menu() {
    // Only add menu if BuddyPress is active
    if ( ! function_exists( ‘buddypress’ ) ) {
    return;
    }

    add_management_page(
    ‘Delete All BP Notifications’,
    ‘Delete BP Notifications’,
    ‘manage_options’,
    ‘delete-bp-notifications’,
    array( $this, ‘admin_page_content’ )
    );
    }

    public function admin_page_content() {
    if ( ! current_user_can( ‘manage_options’ ) ) {
    return;
    }

    ?>
    <div class=”wrap”>
    <h1>Delete All BuddyPress Notifications</h1>

    <?php if ( ! function_exists( ‘buddypress’ ) ) : ?>
    <div class=”notice notice-error”>
    <p>BuddyPress is not active. This plugin requires BuddyPress to function.</p>
    </div>
    <?php else : ?>
    <div class=”card”>
    <h2 class=”title”>Warning</h2>
    <p>This action will permanently delete <strong>all</strong> BuddyPress notifications for every user on your site. This operation cannot be undone.</p>
    <p>Total notifications in system: <strong><?php echo esc_html( $this->get_notifications_count() ); ?></strong></p>
    </div>

    <form method=”post” action=”<?php echo esc_url( admin_url( ‘admin-post.php’ ) ); ?>” id=”bp-delete-notifications-form”>
    <?php wp_nonce_field( ‘delete_all_bp_notifications_action’, ‘delete_all_bp_notifications_nonce’ ); ?>
    <input type=”hidden” name=”action” value=”delete_all_bp_notifications”>
    <p>
    <input type=”submit” class=”button button-danger” value=”Delete All Notifications”
    onclick=”return confirm(‘Are you absolutely sure? This will permanently delete ALL notifications.’)”>
    </p>
    </form>
    <?php endif; ?>
    </div>
    <?php
    }

    public function handle_delete_notifications() {
    // Security checks
    if ( ! current_user_can( ‘manage_options’ ) ) {
    wp_die( esc_html__( ‘Permission denied’, ‘buddypress-delete-notifications’ ) );
    }

    check_admin_referer( ‘delete_all_bp_notifications_action’, ‘delete_all_bp_notifications_nonce’ );

    // BuddyPress check
    if ( ! function_exists( ‘buddypress’ ) ) {
    wp_die( esc_html__( ‘BuddyPress is not active’, ‘buddypress-delete-notifications’ ) );
    }

    global $wpdb;
    $table_name = $wpdb->prefix . ‘bp_notifications’;

    // Verify table exists
    if ( $wpdb->get_var( $wpdb->prepare( “SHOW TABLES LIKE %s”, $table_name ) ) !== $table_name ) {
    wp_redirect( admin_url( ‘tools.php?page=delete-bp-notifications&error=no_table’ ) );
    exit;
    }

    // Perform deletion
    $result = $wpdb->query( “TRUNCATE TABLE $table_name” );

    if ( false === $result ) {
    wp_redirect( admin_url( ‘tools.php?page=delete-bp-notifications&error=delete_failed’ ) );
    } else {
    wp_redirect( admin_url( ‘tools.php?page=delete-bp-notifications&deleted=1’ ) );
    }
    exit;
    }

    public function admin_notices() {
    if ( ! isset( $_GET[‘page’] ) || ‘delete-bp-notifications’ !== $_GET[‘page’] ) {
    return;
    }

    if ( isset( $_GET[‘deleted’] ) ) {
    ?>
    <div class=”notice notice-success is-dismissible”>
    <p>All BuddyPress notifications have been successfully deleted.</p>
    </div>
    <?php
    }

    if ( isset( $_GET[‘error’] ) ) {
    $error = sanitize_text_field( $_GET[‘error’] );
    switch ( $error ) {
    case ‘no_table’:
    $message = ‘BuddyPress notifications table does not exist.’;
    break;
    case ‘delete_failed’:
    $message = ‘Failed to delete notifications. Please check database permissions.’;
    break;
    default:
    $message = ‘An unknown error occurred.’;
    break;
    }
    ?>
    <div class=”notice notice-error is-dismissible”>
    <p>Error: <?php echo esc_html( $message ); ?></p>
    </div>
    <?php
    }
    }

    private function get_notifications_count() {
    global $wpdb;
    $table_name = $wpdb->prefix . ‘bp_notifications’;

    if ( $wpdb->get_var( $wpdb->prepare( “SHOW TABLES LIKE %s”, $table_name ) ) === $table_name ) {
    return (int) $wpdb->get_var( “SELECT COUNT(*) FROM $table_name” );
    }

    return 0;
    }

    public function enqueue_confirmation_script( $hook ) {
    if ( ‘tools_page_delete-bp-notifications’ !== $hook ) {
    return;
    }

    wp_add_inline_script( ‘jquery’, ‘
    jQuery(document).ready(function($) {
    $(“#bp-delete-notifications-form”).on(“submit”, function(e) {
    if (!confirm(“Are you absolutely sure? This will permanently delete ALL notifications.”)) {
    e.preventDefault();
    }
    });
    });
    ‘ );
    }
    }

    new BP_Delete_All_Notifications(); `

  • Participant
    Level: Guru
    Posts: 910
    Tosin on #55584

    UPDATED CODE BELOW

     <?php
    /**
     * Plugin Name: BuddyPress Notification Management Tool
     * Description: Manually or automatically delete BuddyPress notifications based on age or schedule.
     * Version: 2.1
     * License: GPLv2 or later
     * Requires at least: 5.6
     * Requires PHP: 7.4
     * Requires Plugins: buddypress
     */
    defined( 'ABSPATH' ) || exit;
    
    class BP_Delete_All_Notifications_Auto {
    
        private $opt_schedule = 'bp_delete_notifications_schedule';
        private $opt_days     = 'bp_delete_notifications_days';
        const CRON_HOOK = 'bp_delete_all_notifications_cron';
    
        public function __construct() {
            add_action( 'admin_menu', [ $this, 'add_admin_menu' ] );
            add_action( 'admin_post_delete_all_bp_notifications', [ $this, 'handle_delete_notifications' ] );
            add_action( 'admin_notices', [ $this, 'admin_notices' ] );
            add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_confirmation_script' ] );
            add_action( 'admin_init', [ $this, 'register_settings' ] );
            add_action( self::CRON_HOOK, [ $this, 'cron_cleanup' ] );
            add_filter( 'cron_schedules', [ $this, 'filter_cron_schedules' ] );
            register_activation_hook( __FILE__, [ $this, 'activate' ] );
            register_deactivation_hook( __FILE__, [ $this, 'deactivate' ] );
            $this->maybe_schedule_cron();
        }
    
        public function add_admin_menu() {
            if ( ! function_exists( 'buddypress' ) ) return;
            add_submenu_page(
                'tools.php',
                'BP Notification Cleanup',
                'BP Notification Cleanup',
                'manage_options',
                'delete-bp-notifications',
                [ $this, 'admin_page_content' ]
            );
        }
    
        public function admin_page_content() {
            if ( ! current_user_can( 'manage_options' ) ) return;
            $schedule = get_option( $this->opt_schedule, 'none' );
            $days     = get_option( $this->opt_days, 30 );
            ?>
            <div class="wrap">
                <h1>BuddyPress Notification Cleanup</h1>
                <?php if ( ! function_exists( 'buddypress' ) ) : ?>
                    <div class="notice notice-error"><p>BuddyPress is not active.</p></div>
                <?php else : ?>
                    <div class="card">
                        <h2>Manual Delete</h2>
                        <p>This will permanently delete <strong>all</strong> notifications for all users.</p>
                        <p>Total Notifications: <strong><?php echo esc_html( $this->get_notifications_count() ); ?></strong></p>
                        <form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>" id="bp-delete-notifications-form">
                            <?php wp_nonce_field( 'delete_all_bp_notifications_action', 'delete_all_bp_notifications_nonce' ); ?>
                            <input type="hidden" name="action" value="delete_all_bp_notifications">
                            <p><input type="submit" class="button button-danger" value="Delete All Notifications" onclick="return confirm('This cannot be undone. Proceed?')"></p>
                        </form>
                    </div>
                    <hr>
                    <div class="card">
                        <h2>Auto Cleanup Settings</h2>
                        <form method="post" action="options.php">
                            <?php
                            settings_fields( 'bp_notifications_cleanup_settings' );
                            do_settings_sections( 'bp_notifications_cleanup' );
                            submit_button( 'Save Settings' );
                            ?>
                        </form>
                        <p>Schedule: <strong><?php echo esc_html( ucfirst( $schedule ) ); ?></strong></p>
                        <p>Retention Days: <strong><?php echo esc_html( $days ); ?></strong> (Notifications older than this will be deleted.)</p>
                    </div>
                <?php endif; ?>
            </div>
            <?php
        }
    
        public function handle_delete_notifications() {
            if ( ! current_user_can( 'manage_options' ) ) wp_die( 'Permission denied' );
            check_admin_referer( 'delete_all_bp_notifications_action', 'delete_all_bp_notifications_nonce' );
            $this->perform_truncate();
            wp_redirect( admin_url( 'tools.php?page=delete-bp-notifications&deleted=1' ) );
            exit;
        }
    
        public function perform_truncate() {
            global $wpdb;
            $table = $wpdb->prefix . 'bp_notifications';
            if ( $wpdb->get_var( $wpdb->prepare( "SHOW TABLES LIKE %s", $table ) ) === $table ) {
                $wpdb->query( "TRUNCATE TABLE $table" );
            }
        }
    
        public function cron_cleanup() {
            global $wpdb;
            $table = $wpdb->prefix . 'bp_notifications';
            if ( $wpdb->get_var( $wpdb->prepare( "SHOW TABLES LIKE %s", $table ) ) !== $table ) return;
            $days = absint( get_option( $this->opt_days, 30 ) );
            if ( $days > 0 ) {
                $wpdb->query(
                    $wpdb->prepare(
                        "DELETE FROM {$table} WHERE date_notified < ( NOW() - INTERVAL %d DAY )",
                        $days
                    )
                );
            }
        }
    
        public function admin_notices() {
            if ( isset( $_GET['page'] ) && $_GET['page'] === 'delete-bp-notifications' && isset( $_GET['deleted'] ) ) {
                echo '<div class="notice notice-success is-dismissible"><p>All BuddyPress notifications deleted.</p></div>';
            }
        }
    
        public function enqueue_confirmation_script( $hook ) {
            if ( $hook !== 'tools_page_delete-bp-notifications' ) return;
            wp_add_inline_script( 'jquery', '
                jQuery("#bp-delete-notifications-form").on("submit", function(e){
                    if(!confirm("This will permanently delete ALL notifications. Continue?")) e.preventDefault();
                });
            ' );
        }
    
        private function get_notifications_count() {
            global $wpdb;
            $table = $wpdb->prefix . 'bp_notifications';
            return $wpdb->get_var( $wpdb->prepare( "SHOW TABLES LIKE %s", $table ) ) === $table
                ? (int) $wpdb->get_var( "SELECT COUNT(*) FROM $table" )
                : 0;
        }
    
        public function register_settings() {
            register_setting( 'bp_notifications_cleanup_settings', $this->opt_schedule, [
                'type'              => 'string',
                'sanitize_callback' => [ $this, 'sanitize_schedule' ],
                'default'           => 'none',
            ] );
            register_setting( 'bp_notifications_cleanup_settings', $this->opt_days, [
                'type'              => 'integer',
                'sanitize_callback' => 'absint',
                'default'           => 30,
            ] );
            add_settings_section( 'bp_notifications_cleanup_section', 'Cleanup Options', null, 'bp_notifications_cleanup' );
            add_settings_field( 'schedule', 'Cleanup Schedule', [ $this, 'schedule_field_cb' ], 'bp_notifications_cleanup', 'bp_notifications_cleanup_section' );
            add_settings_field( 'days', 'Retention Days', [ $this, 'days_field_cb' ], 'bp_notifications_cleanup', 'bp_notifications_cleanup_section' );
        }
    
        public function schedule_field_cb() {
            $v = get_option( $this->opt_schedule, 'none' );
            echo '<select name="'.esc_attr($this->opt_schedule).'">
                <option value="none"'.selected($v,'none',false).'>None</option>
                <option value="daily"'.selected($v,'daily',false).'>Daily</option>
                <option value="weekly"'.selected($v,'weekly',false).'>Weekly</option>
                <option value="monthly"'.selected($v,'monthly',false).'>Monthly</option>
            </select>';
        }
    
        public function days_field_cb() {
            $v = get_option( $this->opt_days, 30 );
            echo '<input type="number" name="'.esc_attr($this->opt_days).'" value="'.esc_attr($v).'" min="1" />';
        }
    
        public function sanitize_schedule( $v ) {
            $allowed = [ 'none','daily','weekly','monthly' ];
            $v = in_array( $v, $allowed, true ) ? $v : 'none';
            $this->reschedule_cron( $v );
            return $v;
        }
    
        private function maybe_schedule_cron() {
            $schedule = get_option( $this->opt_schedule, 'none' );
            if ( $schedule !== 'none' && ! wp_next_scheduled( self::CRON_HOOK ) ) {
                wp_schedule_event( time(), $schedule, self::CRON_HOOK );
            }
        }
    
        private function reschedule_cron( $new ) {
            wp_clear_scheduled_hook( self::CRON_HOOK );
            if ( $new !== 'none' ) wp_schedule_event( time(), $new, self::CRON_HOOK );
        }
    
        public function filter_cron_schedules( $s ) {
            $s['monthly'] = [ 'interval' => 30*DAY_IN_SECONDS, 'display' => 'Once Monthly' ];
            return $s;
        }
    
        public function activate() {
            $this->maybe_schedule_cron();
        }
    
        public function deactivate() {
            wp_clear_scheduled_hook( self::CRON_HOOK );
        }
    }
    
    new BP_Delete_All_Notifications_Auto(); 

    Thanks

  • Keymaster
    (BuddyDev Team)
    Posts: 25076
    Brajesh Singh on #55587

    Hi Tosin,
    Thank you for sharing the code.
    Overall, It looks good to me. I have a few suggestions for improvement though.
    1. follow WordPress coding standards. That will help you make the code more maintainable for long run.

    2. Add check for Checking if notifications component is enabled.
    3. Probably delete in chunks instead of delete all older than x days. That will help you avoid timeouts if the site has too many notifications.

    Hope that helps.

    Regards
    Brajesh

  • Participant
    Level: Guru
    Posts: 910
    Tosin on #55598

    Thanks for the feedback and valable insight, kindly see updated code below

     <?php
    /**
     * Plugin Name: BuddyPress Notifications Management Tool
     * Description: Manually or automatically delete BuddyPress user notifications based on age or schedule.
     * Version: 2.3.1
     * License: GPLv2 or later
     * Requires at least: 5.6
     * Requires PHP: 7.4
     * Requires Plugins: buddypress
     */
    
    defined( 'ABSPATH' ) || exit;
    
    class BP_Delete_All_Notifications_Auto {
    
        private $opt_schedule = 'bp_delete_notifications_schedule';
        private $opt_days     = 'bp_delete_notifications_days';
        const CRON_HOOK = 'bp_delete_all_notifications_cron';
        const CHUNK_SIZE = 1000;
    
        public function __construct() {
            add_action( 'admin_menu', array( $this, 'add_admin_menu' ) );
            add_action( 'admin_post_delete_all_bp_notifications', array( $this, 'handle_delete_notifications' ) );
            add_action( 'admin_notices', array( $this, 'admin_notices' ) );
            add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_confirmation_script' ) );
            add_action( 'admin_init', array( $this, 'register_settings' ) );
            add_action( self::CRON_HOOK, array( $this, 'cron_cleanup' ) );
            add_filter( 'cron_schedules', array( $this, 'filter_cron_schedules' ) );
            register_activation_hook( __FILE__, array( $this, 'activate' ) );
            register_deactivation_hook( __FILE__, array( $this, 'deactivate' ) );
            register_uninstall_hook( __FILE__, array( __CLASS__, 'uninstall' ) );
            $this->maybe_schedule_cron();
            
            // BuddyPress dependency notice
            if ( ! function_exists( 'buddypress' ) ) {
                add_action( 'admin_notices', array( $this, 'buddypress_missing_notice' ) );
            }
        }
    
        public function add_admin_menu() {
            // Only show if notifications component is active
            if ( ! $this->is_notifications_component_active() ) {
                return;
            }
    
            add_submenu_page(
                'tools.php',
                __( 'BP Notification Cleanup', 'bp-notifications-cleanup' ),
                __( 'BP Notification Cleanup', 'bp-notifications-cleanup' ),
                'manage_options',
                'delete-bp-notifications',
                array( $this, 'admin_page_content' )
            );
        }
    
        public function admin_page_content() {
            if ( ! current_user_can( 'manage_options' ) ) {
                return;
            }
    
            $schedule = get_option( $this->opt_schedule, 'none' );
            $days     = get_option( $this->opt_days, 30 );
            ?>
            <div class="wrap">
                <h1><?php esc_html_e( 'BuddyPress Notification Cleanup', 'bp-notifications-cleanup' ); ?></h1>
                <?php if ( ! $this->is_notifications_component_active() ) : ?>
                    <div class="notice notice-error">
                        <p><?php esc_html_e( 'BuddyPress Notifications component is not active.', 'bp-notifications-cleanup' ); ?></p>
                    </div>
                <?php else : ?>
                    <div class="card">
                        <h2><?php esc_html_e( 'Manual Delete', 'bp-notifications-cleanup' ); ?></h2>
                        <p><?php esc_html_e( 'This will permanently delete all notifications for all users.', 'bp-notifications-cleanup' ); ?></p>
                        <p><?php esc_html_e( 'Total Notifications:', 'bp-notifications-cleanup' ); ?> 
                            <strong><?php echo esc_html( $this->get_notifications_count() ); ?></strong>
                        </p>
                        <form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>" id="bp-delete-notifications-form">
                            <?php wp_nonce_field( 'delete_all_bp_notifications_action', 'delete_all_bp_notifications_nonce' ); ?>
                            <input type="hidden" name="action" value="delete_all_bp_notifications">
                            <p>
                                <input type="submit" class="button button-danger" value="<?php esc_attr_e( 'Delete All Notifications', 'bp-notifications-cleanup' ); ?>">
                            </p>
                        </form>
                    </div>
                    <hr>
                    <div class="card">
                        <h2><?php esc_html_e( 'Auto Cleanup Settings', 'bp-notifications-cleanup' ); ?></h2>
                        <form method="post" action="options.php">
                            <?php
                            settings_fields( 'bp_notifications_cleanup_settings' );
                            do_settings_sections( 'bp_notifications_cleanup' );
                            submit_button( __( 'Save Settings', 'bp-notifications-cleanup' ) );
                            ?>
                        </form>
                        <p>
                            <?php esc_html_e( 'Schedule:', 'bp-notifications-cleanup' ); ?> 
                            <strong><?php echo esc_html( ucfirst( $schedule ) ); ?></strong>
                        </p>
                        <p>
                            <?php esc_html_e( 'Retention Days:', 'bp-notifications-cleanup' ); ?> 
                            <strong><?php echo esc_html( $days ); ?></strong>
                            (<?php esc_html_e( 'Notifications older than this will be deleted.', 'bp-notifications-cleanup' ); ?>)
                        </p>
                    </div>
                <?php endif; ?>
            </div>
            <?php
        }
    
        public function handle_delete_notifications() {
            if ( ! current_user_can( 'manage_options' ) ) {
                wp_die( esc_html__( 'Permission denied', 'bp-notifications-cleanup' ) );
            }
            
            check_admin_referer( 'delete_all_bp_notifications_action', 'delete_all_bp_notifications_nonce' );
            
            $nonce    = wp_create_nonce( 'notifications_deleted' );
            $redirect = add_query_arg( 
                array( 'deleted' => 1, '_wpnonce' => $nonce ), 
                admin_url( 'tools.php?page=delete-bp-notifications' ) 
            );
            
            try {
                $this->perform_truncate();
            } catch ( Exception $e ) {
                error_log( 'BP Notification Cleanup Error: ' . $e->getMessage() );
                $redirect = add_query_arg( array( 'error' => 1 ), $redirect );
            }
            
            wp_safe_redirect( $redirect );
            exit;
        }
    
        public function perform_truncate() {
            global $wpdb;
            $table = $wpdb->prefix . 'bp_notifications';
            
            if ( ! $this->table_exists( $table ) ) {
                return;
            }
            
            $wpdb->query( "TRUNCATE TABLE <code>&quot; . esc_sql( $table ) . &quot;</code>" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        }
    
        public function cron_cleanup() {
            // Only run if notifications component is active
            if ( ! $this->is_notifications_component_active() ) {
                return;
            }
    
            global $wpdb;
            $table = $wpdb->prefix . 'bp_notifications';
            
            if ( ! $this->table_exists( $table ) ) {
                return;
            }
            
            $days = absint( get_option( $this->opt_days, 30 ) );
            if ( $days <= 0 ) {
                return;
            }
    
            $continue = true;
            $chunk_size = self::CHUNK_SIZE;
    
            while ( $continue ) {
                $query = $wpdb->prepare(
                    "DELETE FROM <code>&quot; . esc_sql( $table ) . &quot;</code> 
                    WHERE date_notified < ( NOW() - INTERVAL %d DAY )
                    LIMIT %d",
                    $days,
                    $chunk_size
                );
                
                $rows_affected = $wpdb->query( $query ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
                
                if ( $rows_affected === false ) {
                    error_log( 'BP Notification Cron Cleanup Error: Database query failed' );
                    break;
                }
                
                if ( $rows_affected < $chunk_size ) {
                    $continue = false;
                }
            }
        }
    
        public function admin_notices() {
            if ( ! isset( $_GET['page'] ) || 'delete-bp-notifications' !== $_GET['page'] ) {
                return;
            }
    
            if ( isset( $_GET['deleted'] ) && isset( $_GET['_wpnonce'] ) && wp_verify_nonce( sanitize_key( $_GET['_wpnonce'] ), 'notifications_deleted' ) ) {
                echo '<div class="notice notice-success is-dismissible"><p>' . 
                     esc_html__( 'All BuddyPress notifications deleted.', 'bp-notifications-cleanup' ) . 
                     '</p></div>';
            }
    
            if ( isset( $_GET['error'] ) ) {
                echo '<div class="notice notice-error is-dismissible"><p>' . 
                     esc_html__( 'Error deleting notifications. Please check logs.', 'bp-notifications-cleanup' ) . 
                     '</p></div>';
            }
        }
        
        public function buddypress_missing_notice() {
            // Skip if we're on our own settings page
            $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
            if ( $screen && isset( $screen->id ) && 'tools_page_delete-bp-notifications' === $screen->id ) {
                return;
            }
            
            echo '<div class="notice notice-error"><p>' . 
                 esc_html__( 'BuddyPress Delete Notifications requires BuddyPress to be installed and activated.', 'bp-notifications-cleanup' ) . 
                 '</p></div>';
        }
    
        public function enqueue_confirmation_script( $hook ) {
            if ( 'tools_page_delete-bp-notifications' !== $hook ) {
                return;
            }
    
            $message = __( 'This will permanently delete ALL notifications. Continue?', 'bp-notifications-cleanup' );
            wp_add_inline_script(
                'jquery',
                'jQuery("#bp-delete-notifications-form").on("submit", function(e){
                    if(!confirm("' . esc_js( $message ) . '")) {
                        e.preventDefault();
                    }
                });'
            );
        }
    
        private function get_notifications_count() {
            global $wpdb;
            $table = $wpdb->prefix . 'bp_notifications';
            
            if ( ! $this->table_exists( $table ) ) {
                return 0;
            }
            
            try {
                return (int) $wpdb->get_var( "SELECT COUNT(*) FROM <code>&quot; . esc_sql( $table ) . &quot;</code>" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
            } catch ( Exception $e ) {
                error_log( 'BP Notification Count Error: ' . $e->getMessage() );
                return 0;
            }
        }
    
        public function register_settings() {
            register_setting(
                'bp_notifications_cleanup_settings',
                $this->opt_schedule,
                array(
                    'type'              => 'string',
                    'sanitize_callback' => array( $this, 'sanitize_schedule' ),
                    'default'           => 'none',
                )
            );
            register_setting(
                'bp_notifications_cleanup_settings',
                $this->opt_days,
                array(
                    'type'              => 'integer',
                    'sanitize_callback' => array( $this, 'sanitize_retention_days' ),
                    'default'           => 30,
                )
            );
            
            add_settings_section(
                'bp_notifications_cleanup_section',
                __( 'Cleanup Options', 'bp-notifications-cleanup' ),
                null,
                'bp_notifications_cleanup'
            );
            
            add_settings_field(
                'schedule',
                __( 'Cleanup Schedule', 'bp-notifications-cleanup' ),
                array( $this, 'schedule_field_cb' ),
                'bp_notifications_cleanup',
                'bp_notifications_cleanup_section'
            );
            
            add_settings_field(
                'days',
                __( 'Retention Days', 'bp-notifications-cleanup' ),
                array( $this, 'days_field_cb' ),
                'bp_notifications_cleanup',
                'bp_notifications_cleanup_section'
            );
        }
    
        public function schedule_field_cb() {
            $v = get_option( $this->opt_schedule, 'none' );
            ?>
            <select name="<?php echo esc_attr( $this->opt_schedule ); ?>">
                <option value="none" <?php selected( $v, 'none' ); ?>>
                    <?php esc_html_e( 'None', 'bp-notifications-cleanup' ); ?>
                </option>
                <option value="daily" <?php selected( $v, 'daily' ); ?>>
                    <?php esc_html_e( 'Daily', 'bp-notifications-cleanup' ); ?>
                </option>
                <option value="weekly" <?php selected( $v, 'weekly' ); ?>>
                    <?php esc_html_e( 'Weekly', 'bp-notifications-cleanup' ); ?>
                </option>
                <option value="monthly" <?php selected( $v, 'monthly' ); ?>>
                    <?php esc_html_e( 'Monthly', 'bp-notifications-cleanup' ); ?>
                </option>
            </select>
            <?php
        }
    
        public function days_field_cb() {
            $v = get_option( $this->opt_days, 30 );
            ?>
            <input type="number" 
                   name="<?php echo esc_attr( $this->opt_days ); ?>" 
                   value="<?php echo esc_attr( $v ); ?>" 
                   min="1" />
            <?php
        }
    
        public function sanitize_schedule( $v ) {
            $allowed = array( 'none', 'daily', 'weekly', 'monthly' );
            $v = in_array( $v, $allowed, true ) ? $v : 'none';
            $this->reschedule_cron( $v );
            return $v;
        }
        
        public function sanitize_retention_days( $days ) {
            return max( 1, absint( $days ) );
        }
    
        private function maybe_schedule_cron() {
            $schedule = get_option( $this->opt_schedule, 'none' );
            if ( 'none' !== $schedule && ! wp_next_scheduled( self::CRON_HOOK ) ) {
                wp_schedule_event( time(), $schedule, self::CRON_HOOK );
            }
        }
    
        private function reschedule_cron( $new_schedule ) {
            wp_clear_scheduled_hook( self::CRON_HOOK );
            if ( 'none' !== $new_schedule ) {
                wp_schedule_event( time(), $new_schedule, self::CRON_HOOK );
            }
        }
    
        public function filter_cron_schedules( $schedules ) {
            $schedules['monthly'] = array(
                'interval' => 30 * DAY_IN_SECONDS,
                'display'  => __( 'Once Monthly (30 days)', 'bp-notifications-cleanup' )
            );
            return $schedules;
        }
    
        private function table_exists( $table ) {
            global $wpdb;
            $result = $wpdb->get_var( $wpdb->prepare(
                "SELECT COUNT(1) 
                 FROM information_schema.tables 
                 WHERE table_schema = %s 
                 AND table_name = %s 
                 LIMIT 1",
                DB_NAME,
                $table
            ) );
            return (bool) $result;
        }
        
        private function is_notifications_component_active() {
            // Check if BuddyPress is active and notifications component is enabled
            return function_exists( 'bp_is_active' ) && bp_is_active( 'notifications' );
        }
    
        public function activate() {
            global $wpdb;
            $table = $wpdb->prefix . 'bp_notifications';
            
            if ( $this->table_exists( $table ) ) {
                $index_exists = $wpdb->get_var( $wpdb->prepare(
                    "SELECT COUNT(1) 
                     FROM information_schema.statistics 
                     WHERE table_schema = %s 
                     AND table_name = %s 
                     AND index_name = 'date_notified'",
                    DB_NAME,
                    $table
                ) );
                
                if ( ! $index_exists ) {
                    $wpdb->query( "ALTER TABLE <code>&quot; . esc_sql( $table ) . &quot;</code> ADD INDEX <code>date_notified</code> (<code>date_notified</code>)" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
                }
            }
            
            $schedule = get_option( $this->opt_schedule, 'none' );
            $this->reschedule_cron( $schedule );
        }
    
        public function deactivate() {
            wp_clear_scheduled_hook( self::CRON_HOOK );
        }
        
        public static function uninstall() {
            delete_option( 'bp_delete_notifications_schedule' );
            delete_option( 'bp_delete_notifications_days' );
            wp_clear_scheduled_hook( self::CRON_HOOK );
        }
    }
    
    new BP_Delete_All_Notifications_Auto(); 
  • Keymaster
    (BuddyDev Team)
    Posts: 25076
    Brajesh Singh on #55607

    Thank you Tosin,
    I will go through the code and get back to you on Tuesday with my feedback.

    Regards
    Brajesh

  • Participant
    Level: Guru
    Posts: 910
    Tosin on #55635

    Gentle Reminder sir,

     <?php
    /**
     * Plugin Name: BuddyPress Notifications Management Tool
     * Description: Manually or automatically delete BuddyPress user notifications based on age or schedule.
     * Version: 2.3.2
     * License: GPLv2 or later
     * Requires at least: 5.6
     * Requires PHP: 7.4
     * Requires Plugins: buddypress
     * Text Domain: bp-notifications-cleanup
     */
    
    defined( 'ABSPATH' ) || exit;
    
    class BP_Delete_All_Notifications_Auto {
    
        private $opt_schedule = 'bp_delete_notifications_schedule';
        private $opt_days     = 'bp_delete_notifications_days';
        const CRON_HOOK = 'bp_delete_all_notifications_cron';
        const CHUNK_SIZE = 1000;
        const NONCE_ACTION = 'delete_all_bp_notifications_action';
        const NONCE_NAME = 'delete_all_bp_notifications_nonce';
    
        public function __construct() {
            add_action( 'admin_menu', array( $this, 'add_admin_menu' ) );
            add_action( 'admin_post_delete_all_bp_notifications', array( $this, 'handle_delete_notifications' ) );
            add_action( 'admin_notices', array( $this, 'admin_notices' ) );
            add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_confirmation_script' ) );
            add_action( 'admin_init', array( $this, 'register_settings' ) );
            add_action( self::CRON_HOOK, array( $this, 'cron_cleanup' ) );
            add_filter( 'cron_schedules', array( $this, 'filter_cron_schedules' ) );
            register_activation_hook( __FILE__, array( $this, 'activate' ) );
            register_deactivation_hook( __FILE__, array( $this, 'deactivate' ) );
            register_uninstall_hook( __FILE__, array( __CLASS__, 'uninstall' ) );
            $this->maybe_schedule_cron();
            
            // BuddyPress dependency notice
            add_action( 'admin_notices', array( $this, 'buddypress_missing_notice' ) );
        }
    
        public function add_admin_menu() {
            if ( ! current_user_can( 'manage_options' ) ) {
                return;
            }
    
            // Only show if notifications component is active
            if ( ! $this->is_notifications_component_active() ) {
                return;
            }
    
            add_submenu_page(
                'tools.php',
                __( 'BP Notification Cleanup', 'bp-notifications-cleanup' ),
                __( 'BP Notification Cleanup', 'bp-notifications-cleanup' ),
                'manage_options',
                'delete-bp-notifications',
                array( $this, 'admin_page_content' )
            );
        }
    
        public function admin_page_content() {
            if ( ! current_user_can( 'manage_options' ) ) {
                return;
            }
    
            $schedule = get_option( $this->opt_schedule, 'none' );
            $days     = get_option( $this->opt_days, 30 );
            ?>
            <div class="wrap">
                <h1><?php esc_html_e( 'BuddyPress Notification Cleanup', 'bp-notifications-cleanup' ); ?></h1>
                <?php if ( ! $this->is_notifications_component_active() ) : ?>
                    <div class="notice notice-error">
                        <p><?php esc_html_e( 'BuddyPress Notifications component is not active.', 'bp-notifications-cleanup' ); ?></p>
                    </div>
                <?php else : ?>
                    <div class="card">
                        <h2><?php esc_html_e( 'Manual Delete', 'bp-notifications-cleanup' ); ?></h2>
                        <p><?php esc_html_e( 'This will permanently delete all notifications for all users.', 'bp-notifications-cleanup' ); ?></p>
                        <p><?php esc_html_e( 'Total Notifications:', 'bp-notifications-cleanup' ); ?> 
                            <strong><?php echo esc_html( number_format_i18n( $this->get_notifications_count() ) ); ?></strong>
                        </p>
                        <form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>" id="bp-delete-notifications-form">
                            <?php wp_nonce_field( self::NONCE_ACTION, self::NONCE_NAME ); ?>
                            <input type="hidden" name="action" value="delete_all_bp_notifications">
                            <p>
                                <input type="submit" class="button button-danger" value="<?php esc_attr_e( 'Delete All Notifications', 'bp-notifications-cleanup' ); ?>">
                            </p>
                        </form>
                    </div>
                    <hr>
                    <div class="card">
                        <h2><?php esc_html_e( 'Auto Cleanup Settings', 'bp-notifications-cleanup' ); ?></h2>
                        <form method="post" action="options.php">
                            <?php
                            settings_fields( 'bp_notifications_cleanup_settings' );
                            do_settings_sections( 'bp_notifications_cleanup' );
                            submit_button( __( 'Save Settings', 'bp-notifications-cleanup' ) );
                            ?>
                        </form>
                        <p>
                            <?php esc_html_e( 'Schedule:', 'bp-notifications-cleanup' ); ?> 
                            <strong><?php echo esc_html( ucfirst( $schedule ) ); ?></strong>
                        </p>
                        <p>
                            <?php esc_html_e( 'Retention Days:', 'bp-notifications-cleanup' ); ?> 
                            <strong><?php echo esc_html( $days ); ?></strong>
                            (<?php esc_html_e( 'Notifications older than this will be deleted.', 'bp-notifications-cleanup' ); ?>)
                        </p>
                        <?php if ( 'none' !== $schedule ) : ?>
                            <p>
                                <?php esc_html_e( 'Next scheduled cleanup:', 'bp-notifications-cleanup' ); ?>
                                <strong>
                                    <?php
                                    $next = wp_next_scheduled( self::CRON_HOOK );
                                    if ($next) {
                                        $format = get_option('date_format') . ' ' . get_option('time_format');
                                        $timestamp = $next + (get_option('gmt_offset') * HOUR_IN_SECONDS);
                                        echo esc_html(date_i18n($format, $timestamp));
                                    } else {
                                        esc_html_e('Not scheduled', 'bp-notifications-cleanup');
                                    }
                                    ?>
                                </strong>
                            </p>
                        <?php endif; ?>
                    </div>
                <?php endif; ?>
            </div>
            <?php
        }
    
        public function handle_delete_notifications() {
            if ( ! current_user_can( 'manage_options' ) ) {
                wp_die( esc_html__( 'Permission denied', 'bp-notifications-cleanup' ) );
            }
            
            check_admin_referer( self::NONCE_ACTION, self::NONCE_NAME );
            
            $nonce    = wp_create_nonce( 'notifications_deleted' );
            $redirect = admin_url( 'tools.php?page=delete-bp-notifications' );
            
            try {
                $this->perform_chunked_deletion();
                $redirect = add_query_arg( array( 'deleted' => 1, '_wpnonce' => $nonce ), $redirect );
            } catch ( Exception $e ) {
                error_log( 'BP Notification Cleanup Error: ' . $e->getMessage() );
                $redirect = add_query_arg( array( 'error' => 1 ), $redirect );
            }
            
            wp_safe_redirect( $redirect );
            exit;
        }
    
        private function perform_chunked_deletion() {
            global $wpdb;
            $table = $wpdb->prefix . 'bp_notifications';
            
            if ( ! $this->table_exists( $table ) ) {
                return;
            }
    
            $continue = true;
            $chunk_size = self::CHUNK_SIZE;
    
            while ( $continue ) {
                $query = "DELETE FROM <code>&quot; . esc_sql( $table ) . &quot;</code> LIMIT {$chunk_size}";
                $rows_affected = $wpdb->query( $query ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
                
                if ( $rows_affected === false ) {
                    throw new Exception( 'Database error during chunked deletion' );
                }
                
                if ( $rows_affected < $chunk_size ) {
                    $continue = false;
                }
                
                // Prevent server overload
                if ( $rows_affected > 0 ) {
                    usleep( 100000 ); // 0.1 second delay between chunks
                }
            }
        }
    
        public function cron_cleanup() {
            // Only run if notifications component is active
            if ( ! $this->is_notifications_component_active() ) {
                return;
            }
    
            global $wpdb;
            $table = $wpdb->prefix . 'bp_notifications';
            
            if ( ! $this->table_exists( $table ) ) {
                return;
            }
            
            $days = absint( get_option( $this->opt_days, 30 ) );
            if ( $days <= 0 ) {
                return;
            }
    
            $continue = true;
            $chunk_size = self::CHUNK_SIZE;
    
            while ( $continue ) {
                $query = $wpdb->prepare(
                    "DELETE FROM <code>&quot; . esc_sql( $table ) . &quot;</code> 
                    WHERE date_notified < ( NOW() - INTERVAL %d DAY )
                    LIMIT %d",
                    $days,
                    $chunk_size
                );
                
                $rows_affected = $wpdb->query( $query ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
                
                if ( $rows_affected === false ) {
                    error_log( 'BP Notification Cron Cleanup Error: Database query failed' );
                    break;
                }
                
                if ( $rows_affected < $chunk_size ) {
                    $continue = false;
                }
                
                // Prevent server overload
                if ( $rows_affected > 0 ) {
                    usleep( 100000 ); // 0.1 second delay between chunks
                }
            }
        }
    
        public function admin_notices() {
            if ( ! isset( $_GET['page'] ) || 'delete-bp-notifications' !== $_GET['page'] ) {
                return;
            }
    
            if ( isset( $_GET['deleted'] ) && isset( $_GET['_wpnonce'] ) && wp_verify_nonce( sanitize_key( $_GET['_wpnonce'] ), 'notifications_deleted' ) ) {
                echo '<div class="notice notice-success is-dismissible"><p>' . 
                     esc_html__( 'All BuddyPress notifications deleted.', 'bp-notifications-cleanup' ) . 
                     '</p></div>';
            }
    
            if ( isset( $_GET['error'] ) ) {
                echo '<div class="notice notice-error is-dismissible"><p>' . 
                     esc_html__( 'Error deleting notifications. Please check logs.', 'bp-notifications-cleanup' ) . 
                     '</p></div>';
            }
        }
        
        public function buddypress_missing_notice() {
            // Skip if BuddyPress is active
            if ( function_exists( 'buddypress' ) ) {
                return;
            }
            
            // Skip if we're on our own settings page
            $screen = get_current_screen();
            if ( $screen && isset( $screen->id ) && 'tools_page_delete-bp-notifications' === $screen->id ) {
                return;
            }
            
            echo '<div class="notice notice-error"><p>' . 
                 esc_html__( 'BuddyPress Delete Notifications requires BuddyPress to be installed and activated.', 'bp-notifications-cleanup' ) . 
                 '</p></div>';
        }
    
        public function enqueue_confirmation_script( $hook ) {
            if ( 'tools_page_delete-bp-notifications' !== $hook ) {
                return;
            }
    
            $message = __( 'This will permanently delete ALL notifications. Continue?', 'bp-notifications-cleanup' );
            wp_add_inline_script(
                'jquery',
                'jQuery(document).ready(function($){
                    $("#bp-delete-notifications-form").on("submit", function(e){
                        if(!confirm("' . esc_js( $message ) . '")) {
                            e.preventDefault();
                        }
                    });
                });'
            );
        }
    
        private function get_notifications_count() {
            global $wpdb;
            $table = $wpdb->prefix . 'bp_notifications';
            
            if ( ! $this->table_exists( $table ) ) {
                return 0;
            }
            
            try {
                return (int) $wpdb->get_var( "SELECT COUNT(*) FROM <code>&quot; . esc_sql( $table ) . &quot;</code>" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
            } catch ( Exception $e ) {
                error_log( 'BP Notification Count Error: ' . $e->getMessage() );
                return 0;
            }
        }
    
        public function register_settings() {
            register_setting(
                'bp_notifications_cleanup_settings',
                $this->opt_schedule,
                array(
                    'type'              => 'string',
                    'sanitize_callback' => array( $this, 'sanitize_schedule' ),
                    'default'           => 'none',
                )
            );
            register_setting(
                'bp_notifications_cleanup_settings',
                $this->opt_days,
                array(
                    'type'              => 'integer',
                    'sanitize_callback' => array( $this, 'sanitize_retention_days' ),
                    'default'           => 30,
                )
            );
            
            add_settings_section(
                'bp_notifications_cleanup_section',
                __( 'Cleanup Options', 'bp-notifications-cleanup' ),
                null,
                'bp_notifications_cleanup'
            );
            
            add_settings_field(
                'schedule',
                __( 'Cleanup Schedule', 'bp-notifications-cleanup' ),
                array( $this, 'schedule_field_cb' ),
                'bp_notifications_cleanup',
                'bp_notifications_cleanup_section'
            );
            
            add_settings_field(
                'days',
                __( 'Retention Days', 'bp-notifications-cleanup' ),
                array( $this, 'days_field_cb' ),
                'bp_notifications_cleanup',
                'bp_notifications_cleanup_section'
            );
        }
    
        public function schedule_field_cb() {
            $v = get_option( $this->opt_schedule, 'none' );
            ?>
            <select name="<?php echo esc_attr( $this->opt_schedule ); ?>">
                <option value="none" <?php selected( $v, 'none' ); ?>>
                    <?php esc_html_e( 'None', 'bp-notifications-cleanup' ); ?>
                </option>
                <option value="daily" <?php selected( $v, 'daily' ); ?>>
                    <?php esc_html_e( 'Daily', 'bp-notifications-cleanup' ); ?>
                </option>
                <option value="weekly" <?php selected( $v, 'weekly' ); ?>>
                    <?php esc_html_e( 'Weekly', 'bp-notifications-cleanup' ); ?>
                </option>
                <option value="monthly" <?php selected( $v, 'monthly' ); ?>>
                    <?php esc_html_e( 'Monthly', 'bp-notifications-cleanup' ); ?>
                </option>
            </select>
            <?php
        }
    
        public function days_field_cb() {
            $v = get_option( $this->opt_days, 30 );
            ?>
            <input type="number" 
                   name="<?php echo esc_attr( $this->opt_days ); ?>" 
                   value="<?php echo esc_attr( $v ); ?>" 
                   min="1" 
                   class="small-text" />
            <p class="description">
                <?php esc_html_e( 'Notifications older than this many days will be deleted during automatic cleanup.', 'bp-notifications-cleanup' ); ?>
            </p>
            <?php
        }
    
        public function sanitize_schedule( $v ) {
            $allowed = array( 'none', 'daily', 'weekly', 'monthly' );
            $v = in_array( $v, $allowed, true ) ? $v : 'none';
            $this->reschedule_cron( $v );
            return $v;
        }
        
        public function sanitize_retention_days( $days ) {
            return max( 1, absint( $days ) );
        }
    
        private function maybe_schedule_cron() {
            $schedule = get_option( $this->opt_schedule, 'none' );
            if ( 'none' !== $schedule && ! wp_next_scheduled( self::CRON_HOOK ) ) {
                wp_schedule_event( time(), $schedule, self::CRON_HOOK );
            }
        }
    
        private function reschedule_cron( $new_schedule ) {
            wp_clear_scheduled_hook( self::CRON_HOOK );
            if ( 'none' !== $new_schedule ) {
                wp_schedule_event( time(), $new_schedule, self::CRON_HOOK );
            }
        }
    
        public function filter_cron_schedules( $schedules ) {
            if ( ! isset( $schedules['monthly'] ) ) {
                $schedules['monthly'] = array(
                    'interval' => 30 * DAY_IN_SECONDS,
                    'display'  => __( 'Once Monthly (30 days)', 'bp-notifications-cleanup' )
                );
            }
            return $schedules;
        }
    
        private function table_exists( $table ) {
            global $wpdb;
            return $wpdb->get_var( $wpdb->prepare( 
                "SHOW TABLES LIKE %s", 
                $wpdb->esc_like( $table ) 
            ) ) === $table;
        }
        
        private function is_notifications_component_active() {
            return function_exists( 'bp_is_active' ) && bp_is_active( 'notifications' );
        }
    
        public function activate() {
            global $wpdb;
            $table = $wpdb->prefix . 'bp_notifications';
            
            if ( $this->table_exists( $table ) ) {
                // Check if index exists using SHOW INDEX
                $index_exists = $wpdb->get_var( $wpdb->prepare(
                    "SHOW INDEX FROM <code>$table</code> WHERE Key_name = 'date_notified'"
                ) );
                
                if ( ! $index_exists ) {
                    $wpdb->query( "ALTER TABLE <code>$table</code> ADD INDEX <code>date_notified</code> (<code>date_notified</code>)" );
                }
            }
            
            $schedule = get_option( $this->opt_schedule, 'none' );
            $this->reschedule_cron( $schedule );
        }
    
        public function deactivate() {
            wp_clear_scheduled_hook( self::CRON_HOOK );
        }
        
        public static function uninstall() {
            delete_option( 'bp_delete_notifications_schedule' );
            delete_option( 'bp_delete_notifications_days' );
            wp_clear_scheduled_hook( self::CRON_HOOK );
        }
    }
    
    new BP_Delete_All_Notifications_Auto(); 

    Thanks

  • Keymaster
    (BuddyDev Team)
    Posts: 25076
    Brajesh Singh on #55649

    Hi Tosin,
    Thank you for sharing the update.

    Please allow me to go through it over the weekend and get back to you.

    Regards
    Brajesh

You must be logged in to reply to this topic.

This topic is: not resolved