Skip to:
Content

BuddyPress.org

Ticket #8448: 8448.4.patch

File 8448.4.patch, 68.1 KB (added by dcavins, 3 months ago)

Combination of .3.patch with imath's additional patches plus correct behavior in network admin.

  • src/bp-core/admin/bp-core-admin-functions.php

    diff --git src/bp-core/admin/bp-core-admin-functions.php src/bp-core/admin/bp-core-admin-functions.php
    index b81786427..7749dd449 100644
    function bp_core_modify_admin_menu_highlight() { 
    8484        if ( in_array( $plugin_page, array( 'bp-tools', 'available-tools' ) ) ) {
    8585                $submenu_file = $plugin_page;
    8686        }
     87
     88        // Keep the BuddyPress tools menu highlighted.
     89        if ( 'bp-optouts' === $plugin_page ) {
     90                $submenu_file = 'bp-tools';
     91        }
    8792}
    8893
    8994/**
    function bp_do_activation_redirect() { 
    389394 * Output the tabs in the admin area.
    390395 *
    391396 * @since 1.5.0
     397 * @since 8.0.0 Adds the `$context` parameter.
    392398 *
    393399 * @param string $active_tab Name of the tab that is active. Optional.
     400 * @param string $context    The context of use for the tabs. Defaults to 'settings'.
     401 *                           Possible values are 'settings' & 'tools'.
    394402 */
    395 function bp_core_admin_tabs( $active_tab = '' ) {
     403function bp_core_admin_tabs( $active_tab = '', $context = 'settings' ) {
    396404        $tabs_html    = '';
    397405        $idle_class   = 'nav-tab';
    398406        $active_class = 'nav-tab nav-tab-active';
    function bp_core_admin_tabs( $active_tab = '' ) { 
    404412         *
    405413         * @param array $value Array of tabs to output to the admin area.
    406414         */
    407         $tabs = apply_filters( 'bp_core_admin_tabs', bp_core_get_admin_tabs( $active_tab ) );
     415        $tabs = apply_filters( 'bp_core_admin_tabs', bp_core_get_admin_tabs( $active_tab, $context ) );
    408416
    409417        // Loop through tabs and build navigation.
    410418        foreach ( array_values( $tabs ) as $tab_data ) {
    function bp_core_admin_tabs( $active_tab = '' ) { 
    419427         * Fires after the output of tabs for the admin area.
    420428         *
    421429         * @since 1.5.0
     430         * @since 8.0.0 Adds the `$context` parameter.
     431         *
     432         * @param string $context The context of use for the tabs.
    422433         */
    423         do_action( 'bp_admin_tabs' );
     434        do_action( 'bp_admin_tabs', $context );
    424435}
    425436
    426437/**
    427438 * Get the data for the tabs in the admin area.
    428439 *
    429440 * @since 2.2.0
     441 * @since 8.0.0 Adds the `$context` parameter.
    430442 *
    431443 * @param string $active_tab Name of the tab that is active. Optional.
     444 * @param string $context    The context of use for the tabs. Defaults to 'settings'.
     445 *                           Possible values are 'settings' & 'tools'.
    432446 * @return string
    433447 */
    434 function bp_core_get_admin_tabs( $active_tab = '' ) {
    435         $tabs = array(
    436                 '0' => array(
    437                         'href' => bp_get_admin_url( add_query_arg( array( 'page' => 'bp-components' ), 'admin.php' ) ),
    438                         'name' => __( 'Components', 'buddypress' ),
    439                 ),
    440                 '2' => array(
    441                         'href' => bp_get_admin_url( add_query_arg( array( 'page' => 'bp-settings' ), 'admin.php' ) ),
    442                         'name' => __( 'Options', 'buddypress' ),
    443                 ),
    444                 '1' => array(
    445                         'href' => bp_get_admin_url( add_query_arg( array( 'page' => 'bp-page-settings' ), 'admin.php' ) ),
    446                         'name' => __( 'Pages', 'buddypress' ),
    447                 ),
    448                 '3' => array(
    449                         'href' => bp_get_admin_url( add_query_arg( array( 'page' => 'bp-credits' ), 'admin.php' ) ),
    450                         'name' => __( 'Credits', 'buddypress' ),
    451                 ),
    452         );
     448function bp_core_get_admin_tabs( $active_tab = '', $context = 'settings' ) {
     449        $tabs = array();
     450
     451        if ( 'settings' === $context ) {
     452                $tabs = array(
     453                        '0' => array(
     454                                'href' => bp_get_admin_url( add_query_arg( array( 'page' => 'bp-components' ), 'admin.php' ) ),
     455                                'name' => __( 'Components', 'buddypress' ),
     456                        ),
     457                        '2' => array(
     458                                'href' => bp_get_admin_url( add_query_arg( array( 'page' => 'bp-settings' ), 'admin.php' ) ),
     459                                'name' => __( 'Options', 'buddypress' ),
     460                        ),
     461                        '1' => array(
     462                                'href' => bp_get_admin_url( add_query_arg( array( 'page' => 'bp-page-settings' ), 'admin.php' ) ),
     463                                'name' => __( 'Pages', 'buddypress' ),
     464                        ),
     465                        '3' => array(
     466                                'href' => bp_get_admin_url( add_query_arg( array( 'page' => 'bp-credits' ), 'admin.php' ) ),
     467                                'name' => __( 'Credits', 'buddypress' ),
     468                        ),
     469                );
     470        } elseif ( 'tools' === $context ) {
     471                $tools_page = 'tools.php';
     472                if ( bp_core_do_network_admin() ) {
     473                        $tools_page = 'admin.php';
     474                }
     475
     476                $tabs = array(
     477                        '0' => array(
     478                                'href' => bp_get_admin_url( add_query_arg( array( 'page' => 'bp-tools' ), $tools_page ) ),
     479                                'name' => __( 'Repair', 'buddypress' ),
     480                        ),
     481                        '1' => array(
     482                                'href' => bp_get_admin_url( add_query_arg( array( 'page' => 'bp-optouts' ), $tools_page ) ),
     483                                'name' => __( 'Manage Opt-outs', 'buddypress' ),
     484                        ),
     485                );
     486        }
    453487
    454488        /**
    455489         * Filters the tab data used in our wp-admin screens.
    456490         *
    457491         * @since 2.2.0
     492         * @since 8.0.0 Adds the `$context` parameter.
    458493         *
    459          * @param array $tabs Tab data.
     494         * @param array  $tabs    Tab data.
     495         * @param string $context The context of use for the tabs.
    460496         */
    461         return apply_filters( 'bp_core_get_admin_tabs', $tabs );
     497        return apply_filters( 'bp_core_get_admin_tabs', $tabs, $context );
    462498}
    463499
    464500/** Help **********************************************************************/
  • new file src/bp-core/admin/bp-core-admin-optouts.php

    diff --git src/bp-core/admin/bp-core-admin-optouts.php src/bp-core/admin/bp-core-admin-optouts.php
    new file mode 100644
    index 000000000..68fcf3c3c
    - +  
     1<?php
     2/**
     3 * BuddyPress Opt-outs management.
     4 *
     5 * @package BuddyPress
     6 * @subpackage Core
     7 * @since 8.0.0
     8 */
     9
     10// Exit if accessed directly.
     11defined( 'ABSPATH' ) || exit;
     12
     13/**
     14 * Set up the Opt-outs admin page.
     15 *
     16 * Loaded before the page is rendered, this function does all initial
     17 * setup, including: processing form requests, registering contextual
     18 * help, and setting up screen options.
     19 *
     20 * @since 8.0.0
     21 *
     22 * @global $bp_optouts_list_table
     23 */
     24function bp_core_optouts_admin_load() {
     25        global $bp_optouts_list_table;
     26
     27        // Build redirection URL.
     28        $redirect_to = remove_query_arg( array( 'action', 'error', 'updated', 'activated', 'notactivated', 'deleted', 'notdeleted', 'resent', 'notresent', 'do_delete', 'do_resend', 'do_activate', '_wpnonce', 'signup_ids' ), $_SERVER['REQUEST_URI'] );
     29        $doaction    = bp_admin_list_table_current_bulk_action();
     30
     31        /**
     32         * Fires at the start of the nonmember opt-outs admin load.
     33         *
     34         * @since 8.0.0
     35         *
     36         * @param string $doaction Current bulk action being processed.
     37         * @param array  $_REQUEST Current $_REQUEST global.
     38         */
     39        do_action( 'bp_optouts_admin_load', $doaction, $_REQUEST );
     40
     41        /**
     42         * Filters the allowed actions for use in the nonmember optouts admin page.
     43         *
     44         * @since 8.0.0
     45         *
     46         * @param array $value Array of allowed actions to use.
     47         */
     48        $allowed_actions = apply_filters( 'bp_optouts_admin_allowed_actions', array( 'do_delete',  'do_resend' ) );
     49
     50        if ( ! in_array( $doaction, $allowed_actions ) || ( -1 == $doaction ) ) {
     51
     52                require_once( ABSPATH . 'wp-admin/includes/class-wp-users-list-table.php' );
     53                $bp_optouts_list_table = new BP_Optouts_List_Table();
     54
     55                // The per_page screen option.
     56                add_screen_option( 'per_page', array( 'label' => _x( 'Nonmember opt-outs', 'Nonmember opt-outs per page (screen options)', 'buddypress' ) ) );
     57
     58                // Current screen.
     59                $current_screen = get_current_screen();
     60
     61                $current_screen->add_help_tab(
     62                        array(
     63                                'id'      => 'bp-optouts-overview',
     64                                'title'   => __( 'Overview', 'buddypress' ),
     65                                'content' =>
     66                                        '<p>' . __( 'This is the administration screen for nonmember opt-outs on your site.', 'buddypress' ) . '</p>' .
     67                                        '<p>' . __( 'From the screen options, you can customize the displayed columns and the pagination of this screen.', 'buddypress' ) . '</p>' .
     68                                        '<p>' . __( 'You can reorder the list of opt-outs by clicking on the Email Sender, Email Type or Date Modified column headers.', 'buddypress' ) . '</p>' .
     69                                        '<p>' . __( 'Using the search form, you can search for an opt-out to a specific email address.', 'buddypress' ) . '</p>',
     70                        )
     71                );
     72
     73                $current_screen->add_help_tab(
     74                        array(
     75                                'id'      => 'bp-optouts-actions',
     76                                'title'   => __( 'Actions', 'buddypress' ),
     77                                'content' =>
     78                                        '<p>' . __( 'Hovering over a row in the opt-outs list will display action links that allow you to manage the opt-out. You can perform the following actions:', 'buddypress' ) . '</p>' .
     79                                        '<ul><li>' . __( '"Delete" allows you to delete the record of an opt-out. You will be asked to confirm this deletion.', 'buddypress' ) . '</li></ul>' .
     80                                        '<p>' . __( 'Bulk actions allow you to perform these actions for the selected rows.', 'buddypress' ) . '</p>',
     81                        )
     82                );
     83
     84                // Help panel - sidebar links.
     85                $current_screen->set_help_sidebar(
     86                        '<p><strong>' . __( 'For more information:', 'buddypress' ) . '</strong></p>' .
     87                        '<p>' . __( '<a href="https://buddypress.org/support/">Support Forums</a>', 'buddypress' ) . '</p>'
     88                );
     89
     90                // Add accessible hidden headings and text for the Pending Users screen.
     91                $current_screen->set_screen_reader_content(
     92                        array(
     93                                /* translators: accessibility text */
     94                                'heading_views'      => __( 'Filter opt-outs list', 'buddypress' ),
     95                                /* translators: accessibility text */
     96                                'heading_pagination' => __( 'Opt-out list navigation', 'buddypress' ),
     97                                /* translators: accessibility text */
     98                                'heading_list'       => __( 'Opt-outs list', 'buddypress' ),
     99                        )
     100                );
     101
     102        } else {
     103                if ( empty( $_REQUEST['optout_ids' ] ) ) {
     104                        return;
     105                }
     106                $optout_ids = wp_parse_id_list( $_REQUEST['optout_ids' ] );
     107
     108                // Handle optout deletion.
     109                if ( 'do_delete' == $doaction ) {
     110
     111                        // Nonce check.
     112                        check_admin_referer( 'optouts_delete' );
     113
     114                        $success = 0;
     115                        foreach ( $optout_ids as $optout_id ) {
     116                                if ( bp_delete_optout_by_id( $optout_id ) ) {
     117                                        $success++;
     118                                }
     119                        }
     120
     121                        $query_arg = array( 'updated' => 'deleted' );
     122
     123                        if ( ! empty( $success ) ) {
     124                                $query_arg['deleted'] = $success;
     125                        }
     126
     127                        $notdeleted = count( $optout_ids ) - $success;
     128                        if ( $notdeleted > 0 ) {
     129                                $query_arg['notdeleted'] = $notdeleted;
     130                        }
     131
     132                        $redirect_to = add_query_arg( $query_arg, $redirect_to );
     133
     134                        bp_core_redirect( $redirect_to );
     135
     136                // Plugins can update other stuff from here.
     137                } else {
     138
     139                        /**
     140                         * Fires at end of opt-outs admin load
     141                         * if doaction does not match any actions.
     142                         *
     143                         * @since 2.0.0
     144                         *
     145                         * @param string $doaction Current bulk action being processed.
     146                         * @param array  $_REQUEST Current $_REQUEST global.
     147                         * @param string $redirect Determined redirect url to send user to.
     148                         */
     149                        do_action( 'bp_core_admin_update_optouts', $doaction, $_REQUEST, $redirect_to );
     150
     151                        bp_core_redirect( $redirect_to );
     152                }
     153        }
     154}
     155add_action( "load-tools_page_bp-optouts", 'bp_core_optouts_admin_load' );
     156
     157/**
     158 * Get admin notice when viewing the optouts management page.
     159 *
     160 * @since 8.0.0
     161 *
     162 * @return array
     163 */
     164function bp_core_get_optouts_notice() {
     165
     166        // Setup empty notice for return value.
     167        $notice = array();
     168
     169        // Updates.
     170        if ( ! empty( $_REQUEST['updated'] ) && 'deleted' === $_REQUEST['updated'] ) {
     171                $notice = array(
     172                        'class'   => 'updated',
     173                        'message' => ''
     174                );
     175
     176                if ( ! empty( $_REQUEST['deleted'] ) ) {
     177                        $notice['message'] .= sprintf(
     178                                /* translators: %s: number of deleted optouts */
     179                                _nx( '%s opt-out successfully deleted!', '%s opt-outs successfully deleted!',
     180                                 absint( $_REQUEST['deleted'] ),
     181                                 'nonmembers optout deleted',
     182                                 'buddypress'
     183                                ),
     184                                number_format_i18n( absint( $_REQUEST['deleted'] ) )
     185                        );
     186                }
     187
     188                if ( ! empty( $_REQUEST['notdeleted'] ) ) {
     189                        $notice['message'] .= sprintf(
     190                                /* translators: %s: number of optouts that failed to be deleted */
     191                                _nx( '%s opt-out was not deleted.', '%s opt-outs were not deleted.',
     192                                 absint( $_REQUEST['notdeleted'] ),
     193                                 'nonmembers optout not deleted',
     194                                 'buddypress'
     195                                ),
     196                                number_format_i18n( absint( $_REQUEST['notdeleted'] ) )
     197                        );
     198
     199                        if ( empty( $_REQUEST['deleted'] ) ) {
     200                                $notice['class'] = 'error';
     201                        }
     202                }
     203        }
     204
     205        // Errors.
     206        if ( ! empty( $_REQUEST['error'] ) && 'do_delete' === $_REQUEST['error'] ) {
     207                $notice = array(
     208                        'class'   => 'error',
     209                        'message' => esc_html__( 'There was a problem deleting opt-outs. Please try again.', 'buddypress' ),
     210                );
     211        }
     212
     213        return $notice;
     214}
     215
     216/**
     217 * Opt-outs admin page router.
     218 *
     219 * Depending on the context, display
     220 * - the list of optouts,
     221 * - or the delete confirmation screen,
     222 *
     223 * Also prepare the admin notices.
     224 *
     225 * @since 8.0.0
     226 */
     227function bp_core_optouts_admin() {
     228        $doaction = bp_admin_list_table_current_bulk_action();
     229
     230        // Prepare notices for admin.
     231        $notice = bp_core_get_optouts_notice();
     232
     233        // Display notices.
     234        if ( ! empty( $notice ) ) :
     235                if ( 'updated' === $notice['class'] ) : ?>
     236
     237                        <div id="message" class="<?php echo esc_attr( $notice['class'] ); ?> notice is-dismissible">
     238
     239                <?php else: ?>
     240
     241                        <div class="<?php echo esc_attr( $notice['class'] ); ?> notice is-dismissible">
     242
     243                <?php endif; ?>
     244
     245                        <p><?php echo esc_html( $notice['message'] ); ?></p>
     246                </div>
     247
     248        <?php endif;
     249
     250        // Show the proper screen.
     251        switch ( $doaction ) {
     252                case 'delete' :
     253                        bp_core_optouts_admin_manage( $doaction );
     254                        break;
     255
     256                default:
     257                        bp_core_optouts_admin_index();
     258                        break;
     259        }
     260}
     261
     262/**
     263 * This is the list of optouts.
     264 *
     265 * @since 8.0.0
     266 *
     267 * @global $plugin_page
     268 * @global $bp_optouts_list_table
     269 */
     270function bp_core_optouts_admin_index() {
     271        global $plugin_page, $bp_optouts_list_table;
     272
     273        $usersearch = ! empty( $_REQUEST['s'] ) ? stripslashes( $_REQUEST['s'] ) : '';
     274
     275        // Prepare the group items for display.
     276        $bp_optouts_list_table->prepare_items();
     277
     278        if ( is_network_admin() ) {
     279                $form_url = network_admin_url( 'admin.php' );
     280        } else {
     281                $form_url = bp_get_admin_url( 'tools.php' );
     282        }
     283
     284        $form_url = add_query_arg(
     285                array(
     286                        'page' => 'bp-optouts',
     287                ),
     288                $form_url
     289        );
     290
     291        $search_form_url = remove_query_arg(
     292                array(
     293                        'action',
     294                        'deleted',
     295                        'notdeleted',
     296                        'error',
     297                        'updated',
     298                        'delete',
     299                        'activate',
     300                        'activated',
     301                        'notactivated',
     302                        'resend',
     303                        'resent',
     304                        'notresent',
     305                        'do_delete',
     306                        'do_activate',
     307                        'do_resend',
     308                        'action2',
     309                        '_wpnonce',
     310                        'optout_ids'
     311                ),
     312                $_SERVER['REQUEST_URI']
     313        );
     314
     315        ?>
     316
     317        <div class="wrap">
     318                <h1 class="wp-heading-inline"><?php esc_html_e( 'BuddyPress tools', 'buddypress' ); ?></h1>
     319                <hr class="wp-header-end">
     320
     321                <h2 class="nav-tab-wrapper"><?php bp_core_admin_tabs( __( 'Manage Opt-outs', 'buddypress' ), 'tools' ); ?></h2>
     322
     323                <?php
     324                if ( $usersearch ) {
     325                        $num_results = (int) $bp_optouts_list_table->total_items;
     326                        printf( '<p><span class="subtitle">' . esc_html( _n( 'Opt-out with an email address matching &#8220;%s&#8221;', 'Opt-outs with an email address matching &#8220;%s&#8221;', $num_results, 'buddypress' ) ) . '</span></p>', esc_html( $usersearch ) );
     327                }
     328                ?>
     329                <p><?php esc_html_e( 'This table shows opt-out requests from people who are not members of this site, but have been contacted via communication from this site, and wish to receive no further communications.', 'buddypress' ); ?></p>
     330
     331                <?php // Display each opt-out on its own row. ?>
     332                <?php $bp_optouts_list_table->views(); ?>
     333
     334                <form id="bp-optouts-search-form" action="<?php echo esc_url( $search_form_url ) ;?>">
     335                        <input type="hidden" name="page" value="<?php echo esc_attr( $plugin_page ); ?>" />
     336                        <?php $bp_optouts_list_table->search_box( esc_html__( 'Search for a specific email address', 'buddypress' ), 'bp-optouts' ); ?>
     337                </form>
     338
     339                <form id="bp-optouts-form" action="<?php echo esc_url( $form_url );?>" method="post">
     340                        <?php $bp_optouts_list_table->display(); ?>
     341                </form>
     342        </div>
     343<?php
     344}
     345
     346/**
     347 * This is the confirmation screen for actions.
     348 *
     349 * @since 8.0.0
     350 *
     351 * @param string $action Delete or resend optout.
     352 *
     353 * @return null|false
     354 */
     355function bp_core_optouts_admin_manage( $action = '' ) {
     356        $capability = bp_core_do_network_admin() ? 'manage_network_options' : 'manage_options';
     357        if ( ! current_user_can( $capability ) || empty( $action ) ) {
     358                die( '-1' );
     359        }
     360
     361        // Get the IDs from the URL.
     362        $ids = false;
     363        if ( ! empty( $_POST['optout_ids'] ) ) {
     364                $ids = wp_parse_id_list( $_POST['optout_ids'] );
     365        } elseif ( ! empty( $_GET['optout_id'] ) ) {
     366                $ids = absint( $_GET['optout_id'] );
     367        }
     368
     369        if ( empty( $ids ) ) {
     370                return false;
     371        }
     372
     373        // Query for matching optouts, and filter out bad IDs.
     374        $args = array(
     375                'id'     => $ids,
     376        );
     377        $optouts    = bp_get_optouts( $args );
     378        $optout_ids = wp_list_pluck( $optouts, 'id' );
     379
     380        // Check optout IDs and set up strings.
     381        switch ( $action ) {
     382                case 'delete' :
     383                        if ( 1 == count( $optouts ) ) {
     384                                $helper_text = __( 'You are about to delete the following opt-out request:', 'buddypress' );
     385                        } else {
     386                                $helper_text = __( 'You are about to delete the following opt-out requests:', 'buddypress' );
     387                        }
     388                        break;
     389        }
     390
     391        // These arguments are added to all URLs.
     392        $url_args = array( 'page' => 'bp-optouts' );
     393
     394        // These arguments are only added when performing an action.
     395        $action_args = array(
     396                'action'     => 'do_' . $action,
     397                'optout_ids' => implode( ',', $optout_ids )
     398        );
     399
     400        if ( is_network_admin() ) {
     401                $base_url = network_admin_url( 'admin.php' );
     402        } else {
     403                $base_url = bp_get_admin_url( 'tools.php' );
     404        }
     405
     406        $cancel_url = add_query_arg( $url_args, $base_url );
     407        $action_url = wp_nonce_url(
     408                add_query_arg(
     409                        array_merge( $url_args, $action_args ),
     410                        $base_url
     411                ),
     412                'optouts_' . $action
     413        );
     414
     415        ?>
     416
     417        <div class="wrap">
     418                <h1 class="wp-heading-inline"><?php esc_html_e( 'BuddyPress tools', 'buddypress' ); ?></h1>
     419                <hr class="wp-header-end">
     420
     421                <h2 class="nav-tab-wrapper"><?php bp_core_admin_tabs( __( 'Manage Opt-outs', 'buddypress' ), 'tools' ); ?></h2>
     422
     423                <p><?php echo esc_html( $helper_text ); ?></p>
     424
     425                <ol class="bp-optouts-list">
     426                <?php foreach ( $optouts as $optout ) : ?>
     427
     428                        <li>
     429                                <strong><?php echo esc_html( $optout->email_address ) ?></strong>
     430                                <p class="description">
     431                                        <?php
     432                                        $last_modified = mysql2date( 'Y/m/d g:i:s a', $optout->date_modified );
     433                                        /* translators: %s: modification date */
     434                                        printf( esc_html__( 'Date modified: %s', 'buddypress'), $last_modified );
     435                                        ?>
     436                                </p>
     437                        </li>
     438
     439                <?php endforeach; ?>
     440                </ol>
     441
     442                <?php if ( 'delete' === $action ) : ?>
     443
     444                        <p><strong><?php esc_html_e( 'This action cannot be undone.', 'buddypress' ) ?></strong></p>
     445
     446                <?php endif ; ?>
     447
     448                <a class="button-primary" href="<?php echo esc_url( $action_url ); ?>"><?php esc_html_e( 'Confirm', 'buddypress' ); ?></a>
     449                <a class="button" href="<?php echo esc_url( $cancel_url ); ?>"><?php esc_html_e( 'Cancel', 'buddypress' ) ?></a>
     450        </div>
     451
     452        <?php
     453}
  • src/bp-core/admin/bp-core-admin-schema.php

    diff --git src/bp-core/admin/bp-core-admin-schema.php src/bp-core/admin/bp-core-admin-schema.php
    index 7a10e39eb..2dde95a4a 100644
    function bp_core_install( $active_components = false ) { 
    4040        // Install the invitations table.
    4141        bp_core_install_invitations();
    4242
     43        // Install the nonmember opt-outs table.
     44        bp_core_install_nonmember_opt_outs();
     45
    4346        // Notifications.
    4447        if ( !empty( $active_components['notifications'] ) ) {
    4548                bp_core_install_notifications();
    function bp_core_install_invitations() { 
    590593         */
    591594        do_action( 'bp_core_install_invitations' );
    592595}
     596
     597/**
     598 * Install database tables to store opt-out requests from nonmembers.
     599 *
     600 * @since 8.0.0
     601 *
     602 * @uses bp_core_set_charset()
     603 * @uses bp_core_get_table_prefix()
     604 * @uses dbDelta()
     605 */
     606function bp_core_install_nonmember_opt_outs() {
     607        $sql             = array();
     608        $charset_collate = $GLOBALS['wpdb']->get_charset_collate();
     609        $bp_prefix       = bp_core_get_table_prefix();
     610        $optouts_class   = new BP_Optout();
     611        $table_name      = $optouts_class->get_table_name();
     612        $sql = "CREATE TABLE {$table_name} (
     613                id bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY,
     614                email_address_hash varchar(255) NOT NULL,
     615                user_id bigint(20) NOT NULL,
     616                email_type varchar(255) NOT NULL,
     617                date_modified datetime NOT NULL,
     618                KEY user_id (user_id),
     619                KEY email_type (email_type),
     620                KEY date_modified (date_modified)
     621                ) {$charset_collate};";
     622        dbDelta( $sql );
     623
     624        /**
     625         * Fires after BuddyPress adds the nonmember opt-outs table.
     626         *
     627         * @since 8.0.0
     628         */
     629        do_action( 'bp_core_install_nonmember_opt_outs' );
     630}
  • src/bp-core/admin/bp-core-admin-tools.php

    diff --git src/bp-core/admin/bp-core-admin-tools.php src/bp-core/admin/bp-core-admin-tools.php
    index 0d49c0d6f..8141eccbe 100644
    function bp_core_admin_tools() { 
    2222                <h1 class="wp-heading-inline"><?php esc_html_e( 'BuddyPress Tools', 'buddypress' ) ?></h1>
    2323                <hr class="wp-header-end">
    2424
     25                <h2 class="nav-tab-wrapper"><?php bp_core_admin_tabs( __( 'Repair', 'buddypress' ), 'tools' ); ?></h2>
     26
    2527                <p><?php esc_html_e( 'BuddyPress keeps track of various relationships between members, groups, and activity items.', 'buddypress' ); ?></p>
    2628                <p><?php esc_html_e( 'Occasionally these relationships become out of sync, most often after an import, update, or migration.', 'buddypress' ); ?></p>
    2729                <p><?php esc_html_e( 'Use the tools below to manually recalculate these relationships.', 'buddypress' ); ?>
    function bp_core_admin_available_tools_intro() { 
    541543        $page = bp_core_do_network_admin() ? 'admin.php' : 'tools.php' ;
    542544        $url  = add_query_arg( $query_arg, bp_get_admin_url( $page ) );
    543545        ?>
    544         <div class="card tool-box">
     546        <div class="card tool-box bp-tools">
    545547                <h2><?php esc_html_e( 'BuddyPress Tools', 'buddypress' ) ?></h2>
    546                 <p>
    547                         <?php esc_html_e( 'BuddyPress keeps track of various relationships between users, groups, and activity items. Occasionally these relationships become out of sync, most often after an import, update, or migration.', 'buddypress' ); ?>
    548                         <?php
    549                         printf(
    550                                 /* translators: %s: the link to the BuddyPress repair tools */
    551                                 esc_html_x( 'Use the %s to repair these relationships.', 'buddypress tools intro', 'buddypress' ),
    552                                 '<a href="' . esc_url( $url ) . '">' . esc_html__( 'BuddyPress Tools', 'buddypress' ) . '</a>'
    553                         );
    554                         ?>
    555                 </p>
     548
     549                <dl>
     550                        <dt><?php esc_html_e( 'Repair Tools', 'buddypress' ) ?></dt>
     551                        <dd>
     552                                <?php esc_html_e( 'BuddyPress keeps track of various relationships between users, groups, and activity items. Occasionally these relationships become out of sync, most often after an import, update, or migration.', 'buddypress' ); ?>
     553                                <?php
     554                                printf(
     555                                        /* translators: %s: the link to the BuddyPress repair tools */
     556                                        esc_html_x( 'Use the %s to repair these relationships.', 'buddypress tools intro', 'buddypress' ),
     557                                        '<a href="' . esc_url( $url ) . '">' . esc_html__( 'BuddyPress Repair Tools', 'buddypress' ) . '</a>'
     558                                );
     559                                ?>
     560                        </dd>
     561                        <dt><?php esc_html_e( 'Manage Opt-outs', 'buddypress' ) ?></dt>
     562                        <dd>
     563                                <?php esc_html_e( 'BuddyPress stores opt-out requests from people who are not members of this site, but have been contacted via communication from this site, and wish to opt-out from future communication.', 'buddypress' ); ?>
     564                                <?php
     565                                $url = add_query_arg( 'page', 'bp-optouts', bp_get_admin_url( $page ) );
     566                                printf(
     567                                        /* translators: %s: the link to the BuddyPress Nonmember Opt-outs */
     568                                        esc_html_x( 'Visit %s to manage your site&rsquo;s opt-out requests.', 'buddypress opt-outs intro', 'buddypress' ),
     569                                        '<a href="' . esc_url( $url ) . '">' . esc_html__( 'Nonmember Opt-outs', 'buddypress' ) . '</a>'
     570                                );
     571                                ?>
     572                        </dd>
     573                </dl>
    556574        </div>
    557575        <?php
    558576}
  • src/bp-core/admin/css/common.css

    diff --git src/bp-core/admin/css/common.css src/bp-core/admin/css/common.css
    index c0af4884c..631305fba 100644
    TABLE OF CONTENTS: 
    306306        content: "";
    307307}
    308308
     309body.tools-php .bp-tools dt {
     310    font-size: 1.1em;
     311    color: #646970;
     312    font-weight: 600;
     313    margin: 1em 0 0.5em 0;
     314}
     315
     316body.tools-php .bp-tools dd {
     317    margin: 0;
     318}
     319
    309320/*
    310321 * 2.4 Tooltips
    311322 */
  • src/bp-core/bp-core-cache.php

    diff --git src/bp-core/bp-core-cache.php src/bp-core/bp-core-cache.php
    index 12c674241..35d381a50 100644
    function bp_clear_object_type_terms_cache( $type_id = 0, $taxonomy = '' ) { 
    415415add_action( 'bp_type_inserted', 'bp_clear_object_type_terms_cache' );
    416416add_action( 'bp_type_updated', 'bp_clear_object_type_terms_cache' );
    417417add_action( 'bp_type_deleted', 'bp_clear_object_type_terms_cache' );
     418
     419/**
     420 * Resets all incremented bp_optout caches.
     421 *
     422 * @since 8.0.0
     423 */
     424function bp_optouts_reset_cache_incrementor() {
     425        bp_core_reset_incrementor( 'bp_optouts' );
     426}
     427add_action( 'bp_optout_after_save', 'bp_optouts_reset_cache_incrementor' );
     428add_action( 'bp_optout_after_delete', 'bp_optouts_reset_cache_incrementor' );
  • src/bp-core/bp-core-functions.php

    diff --git src/bp-core/bp-core-functions.php src/bp-core/bp-core-functions.php
    index 5ae2a54e5..afb941bf8 100644
    function bp_get_widget_max_count_limit( $widget_class = '' ) { 
    42764276         */
    42774277        return apply_filters( 'bp_get_widget_max_count_limit', 50, $widget_class );
    42784278}
     4279
     4280/**
     4281 * Add a new BP_Optout.
     4282 *
     4283 * @since 8.0.0
     4284 *
     4285 * @param array $args {
     4286 *     An array of arguments describing the new opt-out.
     4287 *     @type string $email_address Email address of user who has opted out.
     4288 *     @type int    $user_id       Optional. ID of user whose communication
     4289 *                                 prompted the user to opt-out.
     4290 *     @type string $email_type    Optional. Name of the email type that
     4291 *                                 prompted the user to opt-out.
     4292 *     @type string $date_modified Optional. Specify a time, else now will be used.
     4293 * }
     4294 * @return false|int False on failure, ID of new (or existing) opt-out if successful.
     4295 */
     4296function bp_add_optout( $args = array() ) {
     4297        $optout = new BP_Optout();
     4298        $r      = bp_parse_args(
     4299                $args, array(
     4300                        'email_address' => '',
     4301                        'user_id'       => 0,
     4302                        'email_type'    => '',
     4303                        'date_modified' => bp_core_current_time(),
     4304                ),
     4305                'add_optout'
     4306        );
     4307
     4308        // Opt-outs must have an email address.
     4309        if ( empty( $r['email_address'] ) ) {
     4310                return false;
     4311        }
     4312
     4313        // Avoid creating duplicate opt-outs.
     4314        $optout_id = $optout->optout_exists(
     4315                array(
     4316                        'email_address' => $r['email_address'],
     4317                        'user_id'       => $r['user_id'],
     4318                        'email_type'    => $r['email_type'],
     4319                )
     4320        );
     4321
     4322        if ( ! $optout_id ) {
     4323                // Set up the new opt-out.
     4324                $optout->email_address = $r['email_address'];
     4325                $optout->user_id       = $r['user_id'];
     4326                $optout->email_type    = $r['email_type'];
     4327                $optout->date_modified = $r['date_modified'];
     4328
     4329                $optout_id = $optout->save();
     4330        }
     4331
     4332        return $optout_id;
     4333}
     4334
     4335/**
     4336 * Find matching BP_Optouts.
     4337 *
     4338 * @since 8.0.0
     4339 *
     4340 * @see BP_Optout::get() for a description of parameters and return values.
     4341 *
     4342 * @param array $args See {@link BP_Optout::get()}.
     4343 * @return array See {@link BP_Optout::get()}.
     4344 */
     4345function bp_get_optouts( $args = array() ) {
     4346        $optout_class = new BP_Optout();
     4347        return $optout_class::get( $args );
     4348}
     4349
     4350/**
     4351 * Delete a BP_Optout by ID.
     4352 *
     4353 * @since 8.0.0
     4354 *
     4355 * @param int $id ID of the optout to delete.
     4356 * @return bool True on success, false on failure.
     4357 */
     4358function bp_delete_optout_by_id( $id = 0 ) {
     4359        $optout_class = new BP_Optout();
     4360        return $optout_class::delete_by_id( $id );
     4361}
  • src/bp-core/bp-core-update.php

    diff --git src/bp-core/bp-core-update.php src/bp-core/bp-core-update.php
    index 7cb66c837..1556f6146 100644
    function bp_update_to_8_0() { 
    651651                        }
    652652                }
    653653        }
     654
     655        bp_core_install_nonmember_opt_outs();
    654656}
    655657
    656658/**
  • src/bp-core/classes/class-bp-admin.php

    diff --git src/bp-core/classes/class-bp-admin.php src/bp-core/classes/class-bp-admin.php
    index 617c7d90a..1e10deab7 100644
    class BP_Admin { 
    123123                require( $this->admin_dir . 'bp-core-admin-components.php' );
    124124                require( $this->admin_dir . 'bp-core-admin-slugs.php'      );
    125125                require( $this->admin_dir . 'bp-core-admin-tools.php'      );
     126                require( $this->admin_dir . 'bp-core-admin-optouts.php'    );
    126127        }
    127128
    128129        /**
    class BP_Admin { 
    301302                        'bp_core_admin_tools'
    302303                );
    303304
     305                $hooks[] = add_submenu_page(
     306                        $tools_parent,
     307                        __( 'Manage Opt-outs', 'buddypress' ),
     308                        __( 'Manage Opt-outs', 'buddypress' ),
     309                        $this->capability,
     310                        'bp-optouts',
     311                        'bp_core_optouts_admin'
     312                );
     313
    304314                // For network-wide configs, add a link to (the root site's) Emails screen.
    305315                if ( is_network_admin() && bp_is_network_activated() ) {
    306316                        $email_labels = bp_get_email_post_type_labels();
    class BP_Admin { 
    523533                // About and Credits pages.
    524534                remove_submenu_page( 'index.php', 'bp-about'   );
    525535                remove_submenu_page( 'index.php', 'bp-credits' );
     536
     537                // Nonmembers Opt-outs page.
     538                if ( is_network_admin() ) {
     539                        remove_submenu_page( 'network-tools', 'bp-optouts' );
     540                } else {
     541                        remove_submenu_page( 'tools.php', 'bp-optouts' );
     542                }
    526543        }
    527544
    528545        /**
  • new file src/bp-core/classes/class-bp-optout.php

    diff --git src/bp-core/classes/class-bp-optout.php src/bp-core/classes/class-bp-optout.php
    new file mode 100644
    index 000000000..90dff5f83
    - +  
     1<?php
     2/**
     3 * BuddyPress Nonmember Opt-out Class
     4 *
     5 * @package BuddyPress
     6 * @subpackage Nonmember Opt-outs
     7 *
     8 * @since 8.0.0
     9 */
     10
     11// Exit if accessed directly.
     12defined( 'ABSPATH' ) || exit;
     13
     14/**
     15 * BuddyPress opt-outs.
     16 *
     17 * Use this class to create, access, edit, or delete BuddyPress Nonmember Opt-outs.
     18 *
     19 * @since 8.0.0
     20 */
     21class BP_Optout {
     22
     23        /**
     24         * The opt-out ID.
     25         *
     26         * @since 8.0.0
     27         * @access public
     28         * @var int
     29         */
     30        public $id;
     31
     32        /**
     33         * The hashed email address of the user that wishes to opt out of
     34         * communications from this site.
     35         *
     36         * @since 8.0.0
     37         * @access public
     38         * @var string
     39         */
     40        public $email_address;
     41
     42        /**
     43         * The ID of the user that generated the contact that resulted in the opt-out.
     44         *
     45         * @since 8.0.0
     46         * @access public
     47         * @var int
     48         */
     49        public $user_id;
     50
     51        /**
     52         * The type of email contact that resulted in the opt-out.
     53         * This should be one of the known BP_Email types.
     54         *
     55         * @since 8.0.0
     56         * @access public
     57         * @var string
     58         */
     59        public $email_type;
     60
     61        /**
     62         * The date the opt-out was last modified.
     63         *
     64         * @since 8.0.0
     65         * @access public
     66         * @var string
     67         */
     68        public $date_modified;
     69
     70        /** Public Methods ****************************************************/
     71
     72        /**
     73         * Constructor method.
     74         *
     75         * @since 8.0.0
     76         *
     77         * @param int $id Optional. Provide an ID to access an existing
     78         *        optout item.
     79         */
     80        public function __construct( $id = 0 ) {
     81                if ( ! empty( $id ) ) {
     82                        $this->id = (int) $id;
     83                        $this->populate();
     84                }
     85        }
     86
     87        /**
     88         * Get the opt-outs table name.
     89         *
     90         * @since 8.0.0
     91         * @access public
     92         * @return string
     93         */
     94        public static function get_table_name() {
     95                return buddypress()->members->table_name_optouts;
     96        }
     97
     98        /**
     99         * Update or insert opt-out details into the database.
     100         *
     101         * @since 8.0.0
     102         *
     103         * @global wpdb $wpdb WordPress database object.
     104         *
     105         * @return bool True on success, false on failure.
     106         */
     107        public function save() {
     108
     109                // Return value
     110                $retval = false;
     111
     112                // Default data and format
     113                $data = array(
     114                        'email_address_hash' => $this->email_address,
     115                        'user_id'            => $this->user_id,
     116                        'email_type'         => sanitize_key( $this->email_type ),
     117                        'date_modified'      => $this->date_modified,
     118                );
     119                $data_format = array( '%s', '%d', '%s', '%s' );
     120
     121                /**
     122                 * Fires before an opt-out is saved.
     123                 *
     124                 * @since 8.0.0
     125                 *
     126                 * @param BP_Optout object $this Characteristics of the opt-out to be saved.
     127                 */
     128                do_action_ref_array( 'bp_optout_before_save', array( &$this ) );
     129
     130                // Update.
     131                if ( ! empty( $this->id ) ) {
     132                        $result = self::_update( $data, array( 'ID' => $this->id ), $data_format, array( '%d' ) );
     133                // Insert.
     134                } else {
     135                        $result = self::_insert( $data, $data_format );
     136                }
     137
     138                // Set the opt-out ID if successful.
     139                if ( ! empty( $result ) && ! is_wp_error( $result ) ) {
     140                        global $wpdb;
     141
     142                        $this->id = $wpdb->insert_id;
     143                        $retval   = $wpdb->insert_id;
     144                }
     145
     146                wp_cache_delete( $this->id, 'bp_optouts' );
     147
     148                /**
     149                 * Fires after an optout is saved.
     150                 *
     151                 * @since 8.0.0
     152                 *
     153                 * @param BP_optout object $this Characteristics of the opt-out just saved.
     154                 */
     155                do_action_ref_array( 'bp_optout_after_save', array( &$this ) );
     156
     157                // Return the result.
     158                return $retval;
     159        }
     160
     161        /**
     162         * Fetch data for an existing opt-out from the database.
     163         *
     164         * @since 8.0.0
     165         *
     166         * @global BuddyPress $bp The one true BuddyPress instance.
     167         * @global wpdb $wpdb WordPress database object.
     168         */
     169        public function populate() {
     170                global $wpdb;
     171                $optouts_table_name = $this->get_table_name();
     172
     173                // Check cache for optout data.
     174                $optout = wp_cache_get( $this->id, 'bp_optouts' );
     175
     176                // Cache missed, so query the DB.
     177                if ( false === $optout ) {
     178                        $optout = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$optouts_table_name} WHERE id = %d", $this->id ) );
     179                        wp_cache_set( $this->id, $optout, 'bp_optouts' );
     180                }
     181
     182                // No optout found so set the ID and bail.
     183                if ( empty( $optout ) || is_wp_error( $optout ) ) {
     184                        $this->id = 0;
     185                        return;
     186                }
     187
     188                $this->email_address = $optout->email_address_hash;
     189                $this->user_id       = (int) $optout->user_id;
     190                $this->email_type    = sanitize_key( $optout->email_type );
     191                $this->date_modified = $optout->date_modified;
     192
     193        }
     194
     195        /** Protected Static Methods ******************************************/
     196
     197        /**
     198         * Create an opt-out entry.
     199         *
     200         * @since 8.0.0
     201         *
     202         * @param array $data {
     203         *     Array of optout data, passed to {@link wpdb::insert()}.
     204         *         @type string $email_address     The hashed email address of the user that wishes to opt out of
     205         *                                     communications from this site.
     206         *         @type int    $user_id           The ID of the user that generated the contact that resulted in the opt-out.
     207         *         @type string $email_type        The type of email contact that resulted in the opt-out.
     208         *         @type string $date_modified     Date the opt-out was last modified.
     209         * }
     210         * @param array $data_format See {@link wpdb::insert()}.
     211         * @return int|false The number of rows inserted, or false on error.
     212         */
     213        protected static function _insert( $data = array(), $data_format = array() ) {
     214                global $wpdb;
     215                // We must hash the email address at insert.
     216                $data['email_address_hash'] = wp_hash( $data['email_address_hash'] );
     217                return $wpdb->insert( BP_Optout::get_table_name(), $data, $data_format );
     218        }
     219
     220        /**
     221         * Update opt-outs.
     222         *
     223         * @since 8.0.0
     224         *
     225         * @see wpdb::update() for further description of paramater formats.
     226         *
     227         * @param array $data         Array of opt-out data to update, passed to
     228         *                            {@link wpdb::update()}. Accepts any property of a
     229         *                            BP_optout object.
     230         * @param array $where        The WHERE params as passed to wpdb::update().
     231         *                            Typically consists of array( 'ID' => $id ) to specify the ID
     232         *                            of the item being updated. See {@link wpdb::update()}.
     233         * @param array $data_format  See {@link wpdb::insert()}.
     234         * @param array $where_format See {@link wpdb::insert()}.
     235         * @return int|false The number of rows updated, or false on error.
     236         */
     237        protected static function _update( $data = array(), $where = array(), $data_format = array(), $where_format = array() ) {
     238                global $wpdb;
     239
     240                // Ensure that a passed email address is hashed.
     241                if ( ! empty( $data['email_address_hash'] ) && is_email( $data['email_address_hash'] ) ) {
     242                        $data['email_address_hash'] = wp_hash( $data['email_address_hash'] );
     243                }
     244
     245                return $wpdb->update( BP_Optout::get_table_name(), $data, $where, $data_format, $where_format );
     246        }
     247
     248        /**
     249         * Delete opt-outs.
     250         *
     251         * @since 8.0.0
     252         *
     253         * @see wpdb::update() for further description of paramater formats.
     254         *
     255         * @param array $where        Array of WHERE clauses to filter by, passed to
     256         *                            {@link wpdb::delete()}. Accepts any property of a
     257         *                            BP_optout object.
     258         * @param array $where_format See {@link wpdb::insert()}.
     259         * @return int|false The number of rows updated, or false on error.
     260         */
     261        protected static function _delete( $where = array(), $where_format = array() ) {
     262                global $wpdb;
     263                return $wpdb->delete( BP_Optout::get_table_name(), $where, $where_format );
     264        }
     265
     266        /**
     267         * Assemble the WHERE clause of a get() SQL statement.
     268         *
     269         * Used by BP_optout::get() to create its WHERE
     270         * clause.
     271         *
     272         * @since 8.0.0
     273         *
     274         * @param array $args See {@link BP_optout::get()} for more details.
     275         * @return string WHERE clause.
     276         */
     277        protected static function get_where_sql( $args = array() ) {
     278                global $wpdb;
     279
     280                $where_conditions = array();
     281                $where            = '';
     282
     283                // id.
     284                if ( false !== $args['id'] ) {
     285                        $id_in                  = implode( ',', wp_parse_id_list( $args['id'] ) );
     286                        $where_conditions['id'] = "id IN ({$id_in})";
     287                }
     288
     289                // email_address.
     290                if ( ! empty( $args['email_address'] ) ) {
     291                        if ( ! is_array( $args['email_address'] ) ) {
     292                                $emails = explode( ',', $args['email_address'] );
     293                        } else {
     294                                $emails = $args['email_address'];
     295                        }
     296
     297                        $email_clean = array();
     298                        foreach ( $emails as $email ) {
     299                                $email_hash    = wp_hash( $email );
     300                                $email_clean[] = $wpdb->prepare( '%s', $email_hash );
     301                        }
     302
     303                        $email_in                          = implode( ',', $email_clean );
     304                        $where_conditions['email_address'] = "email_address_hash IN ({$email_in})";
     305                }
     306
     307                // user_id.
     308                if ( ! empty( $args['user_id'] ) ) {
     309                        $user_id_in                  = implode( ',', wp_parse_id_list( $args['user_id'] ) );
     310                        $where_conditions['user_id'] = "user_id IN ({$user_id_in})";
     311                }
     312
     313                // email_type.
     314                if ( ! empty( $args['email_type'] ) ) {
     315                        if ( ! is_array( $args['email_type'] ) ) {
     316                                $email_types = explode( ',', $args['email_type'] );
     317                        } else {
     318                                $email_types = $args['email_type'];
     319                        }
     320
     321                        $et_clean = array();
     322                        foreach ( $email_types as $et ) {
     323                                $et_clean[] = $wpdb->prepare( '%s', sanitize_key( $et ) );
     324                        }
     325
     326                        $et_in                          = implode( ',', $et_clean );
     327                        $where_conditions['email_type'] = "email_type IN ({$et_in})";
     328                }
     329
     330                // search_terms.
     331                if ( ! empty( $args['search_terms'] ) ) {
     332                        // Matching email_address is an exact match because of the hashing.
     333                        $search_terms_like                = wp_hash( $args['search_terms'] );
     334                        $where_conditions['search_terms'] = $wpdb->prepare( '( email_address_hash LIKE %s )', $search_terms_like );
     335                }
     336
     337                // Custom WHERE.
     338                if ( ! empty( $where_conditions ) ) {
     339                        $where = 'WHERE ' . implode( ' AND ', $where_conditions );
     340                }
     341
     342                return $where;
     343        }
     344
     345        /**
     346         * Assemble the ORDER BY clause of a get() SQL statement.
     347         *
     348         * Used by BP_Optout::get() to create its ORDER BY
     349         * clause.
     350         *
     351         * @since 8.0.0
     352         *
     353         * @param array $args See {@link BP_optout::get()} for more details.
     354         * @return string ORDER BY clause.
     355         */
     356        protected static function get_order_by_sql( $args = array() ) {
     357
     358                $conditions = array();
     359                $retval     = '';
     360
     361                // Order by.
     362                if ( ! empty( $args['order_by'] ) ) {
     363                        $order_by_clean = array();
     364                        $columns        = array( 'id', 'email_address_hash', 'user_id', 'email_type', 'date_modified' );
     365                        foreach ( (array) $args['order_by'] as $key => $value ) {
     366                                if ( in_array( $value, $columns, true ) ) {
     367                                        $order_by_clean[] = $value;
     368                                }
     369                        }
     370                        if ( ! empty( $order_by_clean ) ) {
     371                                $order_by               = implode( ', ', $order_by_clean );
     372                                $conditions['order_by'] = "{$order_by}";
     373                        }
     374                }
     375
     376                // Sort order direction.
     377                if ( ! empty( $args['sort_order'] ) ) {
     378                        $sort_order               = bp_esc_sql_order( $args['sort_order'] );
     379                        $conditions['sort_order'] = "{$sort_order}";
     380                }
     381
     382                // Custom ORDER BY.
     383                if ( ! empty( $conditions['order_by'] ) ) {
     384                        $retval = 'ORDER BY ' . implode( ' ', $conditions );
     385                }
     386
     387                return $retval;
     388        }
     389
     390        /**
     391         * Assemble the LIMIT clause of a get() SQL statement.
     392         *
     393         * Used by BP_Optout::get() to create its LIMIT clause.
     394         *
     395         * @since 8.0.0
     396         *
     397         * @param array $args See {@link BP_optout::get()} for more details.
     398         * @return string LIMIT clause.
     399         */
     400        protected static function get_paged_sql( $args = array() ) {
     401                global $wpdb;
     402
     403                // Setup local variable.
     404                $retval = '';
     405
     406                // Custom LIMIT.
     407                if ( ! empty( $args['page'] ) && ! empty( $args['per_page'] ) ) {
     408                        $page     = absint( $args['page']     );
     409                        $per_page = absint( $args['per_page'] );
     410                        $offset   = $per_page * ( $page - 1 );
     411                        $retval   = $wpdb->prepare( "LIMIT %d, %d", $offset, $per_page );
     412                }
     413
     414                return $retval;
     415        }
     416
     417        /**
     418         * Assemble query clauses, based on arguments, to pass to $wpdb methods.
     419         *
     420         * The insert(), update(), and delete() methods of {@link wpdb} expect
     421         * arguments of the following forms:
     422         *
     423         * - associative arrays whose key/value pairs are column => value, to
     424         *   be used in WHERE, SET, or VALUES clauses
     425         * - arrays of "formats", which tell $wpdb->prepare() which type of
     426         *   value to expect when sanitizing (eg, array( '%s', '%d' ))
     427         *
     428         * This utility method can be used to assemble both kinds of params,
     429         * out of a single set of associative array arguments, such as:
     430         *
     431         *     $args = array(
     432         *         'user_id'    => 4,
     433         *         'email_type' => 'type_string',
     434         *     );
     435         *
     436         * This will be converted to:
     437         *
     438         *     array(
     439         *         'data' => array(
     440         *             'user_id' => 4,
     441         *             'email_type'   => 'type_string',
     442         *         ),
     443         *         'format' => array(
     444         *             '%d',
     445         *             '%s',
     446         *         ),
     447         *     )
     448         *
     449         * which can easily be passed as arguments to the $wpdb methods.
     450         *
     451         * @since 8.0.0
     452         *
     453         * @param array $args Associative array of filter arguments.
     454         *                    See {@BP_optout::get()} for a breakdown.
     455         * @return array Associative array of 'data' and 'format' args.
     456         */
     457        protected static function get_query_clauses( $args = array() ) {
     458                $where_clauses = array(
     459                        'data'   => array(),
     460                        'format' => array(),
     461                );
     462
     463                // id.
     464                if ( ! empty( $args['id'] ) ) {
     465                        $where_clauses['data']['id'] = absint( $args['id'] );
     466                        $where_clauses['format'][]   = '%d';
     467                }
     468
     469                // email_address.
     470                if ( ! empty( $args['email_address'] ) ) {
     471                        $where_clauses['data']['email_address_hash'] = $args['email_address'];
     472                        $where_clauses['format'][]                   = '%s';
     473                }
     474
     475                // user_id.
     476                if ( ! empty( $args['user_id'] ) ) {
     477                        $where_clauses['data']['user_id'] = absint( $args['user_id'] );
     478                        $where_clauses['format'][]        = '%d';
     479                }
     480
     481                // email_type.
     482                if ( ! empty( $args['email_type'] ) ) {
     483                        $where_clauses['data']['email_type'] = $args['email_type'];
     484                        $where_clauses['format'][]           = '%s';
     485                }
     486
     487                return $where_clauses;
     488        }
     489
     490        /** Public Static Methods *********************************************/
     491
     492        /**
     493         * Get opt-outs, based on provided filter parameters.
     494         *
     495         * @since 8.0.0
     496         *
     497         * @param array $args {
     498         *     Associative array of arguments. All arguments but $page and
     499         *     $per_page can be treated as filter values for get_where_sql()
     500         *     and get_query_clauses(). All items are optional.
     501         *     @type int|array    $id                ID of opt-out.
     502         *                                           Can be an array of IDs.
     503         *     @type string|array $email_address     Email address of users who have opted out
     504         *                                                       being queried. Can be an array of addresses.
     505         *     @type int|array    $user_id           ID of user whose communication prompted the
     506         *                                           opt-out. Can be an array of IDs.
     507         *     @type string|array $email_type        Name of the emil type to filter by.
     508         *                                           Can be an array of email types.
     509         *     @type string       $search_terms      Term to match against email_address field.
     510         *     @type string       $order_by          Database column to order by.
     511         *     @type string       $sort_order        Either 'ASC' or 'DESC'.
     512         *     @type int          $page              Number of the current page of results.
     513         *                                           Default: false (no pagination,
     514         *                                           all items).
     515         *     @type int          $per_page          Number of items to show per page.
     516         *                                           Default: false (no pagination,
     517         *                                           all items).
     518         *     @type string       $fields            Which fields to return. Specify 'email_addresses' to
     519         *                                           fetch a list of opt-out email_addresses.
     520         *                                           Specify 'user_ids' to
     521         *                                           fetch a list of opt-out user_ids.
     522         *                                           Specify 'ids' to fetch a list of opt-out IDs.
     523         *                                           Default: 'all' (return BP_Optout objects).
     524         * }
     525         *
     526         * @return array BP_Optout objects | IDs of found opt-outs | Email addresses of matches.
     527         */
     528        public static function get( $args = array() ) {
     529                global $wpdb;
     530                $optouts_table_name = BP_Optout::get_table_name();
     531
     532                // Parse the arguments.
     533                $r = bp_parse_args(
     534                        $args,
     535                        array(
     536                                'id'            => false,
     537                                'email_address' => false,
     538                                'user_id'       => false,
     539                                'email_type'    => false,
     540                                'search_terms'  => '',
     541                                'order_by'      => false,
     542                                'sort_order'    => false,
     543                                'page'          => false,
     544                                'per_page'      => false,
     545                                'fields'        => 'all',
     546                        ),
     547                        'bp_optout_get'
     548                );
     549
     550                $sql = array(
     551                        'select'     => "SELECT",
     552                        'fields'     => '',
     553                        'from'       => "FROM {$optouts_table_name} o",
     554                        'where'      => '',
     555                        'orderby'    => '',
     556                        'pagination' => '',
     557                );
     558
     559                if ( 'user_ids' === $r['fields'] ) {
     560                        $sql['fields'] = "DISTINCT o.user_id";
     561                } else if ( 'email_addresses' === $r['fields'] ) {
     562                        $sql['fields'] = "DISTINCT o.email_address_hash";
     563                } else {
     564                        $sql['fields'] = 'DISTINCT o.id';
     565                }
     566
     567                // WHERE.
     568                $sql['where'] = self::get_where_sql(
     569                        array(
     570                                'id'            => $r['id'],
     571                                'email_address' => $r['email_address'],
     572                                'user_id'       => $r['user_id'],
     573                                'email_type'    => $r['email_type'],
     574                                'search_terms'  => $r['search_terms'],
     575                        )
     576                );
     577
     578                // ORDER BY.
     579                $sql['orderby'] = self::get_order_by_sql(
     580                        array(
     581                                'order_by'   => $r['order_by'],
     582                                'sort_order' => $r['sort_order']
     583                        )
     584                );
     585
     586                // LIMIT %d, %d.
     587                $sql['pagination'] = self::get_paged_sql(
     588                        array(
     589                                'page'     => $r['page'],
     590                                'per_page' => $r['per_page'],
     591                        )
     592                );
     593
     594                $paged_optouts_sql = "{$sql['select']} {$sql['fields']} {$sql['from']} {$sql['where']} {$sql['orderby']} {$sql['pagination']}";
     595
     596                /**
     597                 * Filters the pagination SQL statement.
     598                 *
     599                 * @since 8.0.0
     600                 *
     601                 * @param string $value Concatenated SQL statement.
     602                 * @param array  $sql   Array of SQL parts before concatenation.
     603                 * @param array  $r     Array of parsed arguments for the get method.
     604                 */
     605                $paged_optouts_sql = apply_filters( 'bp_optouts_get_paged_optouts_sql', $paged_optouts_sql, $sql, $r );
     606
     607                $cached = bp_core_get_incremented_cache( $paged_optouts_sql, 'bp_optouts' );
     608                if ( false === $cached ) {
     609                        $paged_optout_ids = $wpdb->get_col( $paged_optouts_sql );
     610                        bp_core_set_incremented_cache( $paged_optouts_sql, 'bp_optouts', $paged_optout_ids );
     611                } else {
     612                        $paged_optout_ids = $cached;
     613                }
     614
     615                // Special return format cases.
     616                if ( in_array( $r['fields'], array( 'ids', 'user_ids' ), true ) ) {
     617                        // We only want the field that was found.
     618                        return array_map( 'intval', $paged_optout_ids );
     619                } else if ( 'email_addresses' === $r['fields'] ) {
     620                        return $paged_optout_ids;
     621                }
     622
     623                $uncached_ids = bp_get_non_cached_ids( $paged_optout_ids, 'bp_optouts' );
     624                if ( $uncached_ids ) {
     625                        $ids_sql = implode( ',', array_map( 'intval', $uncached_ids ) );
     626                        $data_objects = $wpdb->get_results( "SELECT o.* FROM {$optouts_table_name} o WHERE o.id IN ({$ids_sql})" );
     627                        foreach ( $data_objects as $data_object ) {
     628                                wp_cache_set( $data_object->id, $data_object, 'bp_optouts' );
     629                        }
     630                }
     631
     632                $paged_optouts = array();
     633                foreach ( $paged_optout_ids as $paged_optout_id ) {
     634                        $paged_optouts[] = new BP_optout( $paged_optout_id );
     635                }
     636
     637                return $paged_optouts;
     638        }
     639
     640        /**
     641         * Get a count of total optouts matching a set of arguments.
     642         *
     643         * @since 8.0.0
     644         *
     645         * @see BP_optout::get() for a description of
     646         *      arguments.
     647         *
     648         * @param array $args See {@link BP_optout::get()}.
     649         * @return int Count of located items.
     650         */
     651        public static function get_total_count( $args ) {
     652                global $wpdb;
     653                $optouts_table_name = BP_Optout::get_table_name();
     654
     655                // Parse the arguments.
     656                $r  = bp_parse_args(
     657                        $args,
     658                        array(
     659                                'id'            => false,
     660                                'email_address' => false,
     661                                'user_id'       => false,
     662                                'email_type'    => false,
     663                                'search_terms'  => '',
     664                                'order_by'      => false,
     665                                'sort_order'    => false,
     666                                'page'          => false,
     667                                'per_page'      => false,
     668                                'fields'        => 'all',
     669                        ),
     670                        'bp_optout_get_total_count'
     671                );
     672
     673                // Build the query
     674                $select_sql = "SELECT COUNT(*)";
     675                $from_sql   = "FROM {$optouts_table_name}";
     676                $where_sql  = self::get_where_sql( $r );
     677                $sql        = "{$select_sql} {$from_sql} {$where_sql}";
     678
     679                // Return the queried results
     680                return $wpdb->get_var( $sql );
     681        }
     682
     683        /**
     684         * Update optouts.
     685         *
     686         * @since 8.0.0
     687         *
     688         * @see BP_optout::get() for a description of
     689         *      accepted update/where arguments.
     690         *
     691         * @param array $update_args Associative array of fields to update,
     692         *                           and the values to update them to. Of the format
     693         *                           array( 'user_id' => 4, 'email_address' => 'bar@foo.com', ).
     694         * @param array $where_args  Associative array of columns/values, to
     695         *                           determine which rows should be updated. Of the format
     696         *                           array( 'user_id' => 7, 'email_address' => 'bar@foo.com', ).
     697         * @return int|bool Number of rows updated on success, false on failure.
     698         */
     699        public static function update( $update_args = array(), $where_args = array() ) {
     700                $update = self::get_query_clauses( $update_args );
     701                $where  = self::get_query_clauses( $where_args  );
     702
     703                /**
     704                 * Fires before an opt-out is updated.
     705                 *
     706                 * @since 8.0.0
     707                 *
     708                 * @param array $where_args  Associative array of columns/values describing
     709                 *                           opt-outs about to be deleted.
     710                 * @param array $update_args Array of new values.
     711                 */
     712                do_action( 'bp_optout_before_update', $where_args, $update_args );
     713
     714                $retval = self::_update( $update['data'], $where['data'], $update['format'], $where['format'] );
     715
     716                // Clear matching items from the cache.
     717                $cache_args           = $where_args;
     718                $cache_args['fields'] = 'ids';
     719                $maybe_cached_ids     = self::get( $cache_args );
     720                foreach ( $maybe_cached_ids as $invite_id ) {
     721                        wp_cache_delete( $invite_id, 'bp_optouts' );
     722                }
     723
     724                /**
     725                 * Fires after an opt-out is updated.
     726                 *
     727                 * @since 8.0.0
     728                 *
     729                 * @param array $where_args  Associative array of columns/values describing
     730                 *                           opt-outs about to be deleted.
     731                 * @param array $update_args Array of new values.
     732                 */
     733                do_action( 'bp_optout_after_update', $where_args, $update_args );
     734
     735                return $retval;
     736        }
     737
     738        /**
     739         * Delete opt-outs.
     740         *
     741         * @since 8.0.0
     742         *
     743         * @see BP_optout::get() for a description of
     744         *      accepted where arguments.
     745         *
     746         * @param array $args Associative array of columns/values, to determine
     747         *                    which rows should be deleted.  Of the format
     748         *                    array( 'user_id' => 7, 'email_address' => 'bar@foo.com', ).
     749         * @return int|bool Number of rows deleted on success, false on failure.
     750         */
     751        public static function delete( $args = array() ) {
     752                $where = self::get_query_clauses( $args );
     753
     754                /**
     755                 * Fires before an opt-out is deleted.
     756                 *
     757                 * @since 8.0.0
     758                 *
     759                 * @param array $args Characteristics of the opt-outs to be deleted.
     760                 */
     761                do_action( 'bp_optout_before_delete', $args );
     762
     763                // Clear matching items from the cache.
     764                $cache_args           = $args;
     765                $cache_args['fields'] = 'ids';
     766                $maybe_cached_ids     = self::get( $cache_args );
     767                foreach ( $maybe_cached_ids as $invite_id ) {
     768                        wp_cache_delete( $invite_id, 'bp_optouts' );
     769                }
     770
     771                $retval = self::_delete( $where['data'], $where['format'] );
     772
     773                /**
     774                 * Fires after an opt-out is deleted.
     775                 *
     776                 * @since 8.0.0
     777                 *
     778                 * @param array $args Characteristics of the opt-outs just deleted.
     779                 */
     780                do_action( 'bp_optout_after_delete', $args );
     781
     782                return $retval;
     783        }
     784
     785        /** Convenience methods ***********************************************/
     786
     787        /**
     788         * Check whether an invitation exists matching the passed arguments.
     789         *
     790         * @since 5.0.0
     791         *
     792         * @see BP_Optout::get() for a description of accepted parameters.
     793         *
     794         * @return int|bool ID of first found invitation or false if none found.
     795         */
     796        public function optout_exists( $args = array() ) {
     797                $exists = false;
     798
     799                $args['fields'] = 'ids';
     800                $optouts        = BP_Optout::get( $args );
     801                if ( $optouts ) {
     802                        $exists = current( $optouts );
     803                }
     804
     805                return $exists;
     806        }
     807
     808        /**
     809         * Delete a single opt-out by ID.
     810         *
     811         * @since 8.0.0
     812         *
     813         * @see BP_optout::delete() for explanation of
     814         *      return value.
     815         *
     816         * @param int $id ID of the opt-out item to be deleted.
     817         * @return bool True on success, false on failure.
     818         */
     819        public static function delete_by_id( $id ) {
     820                return self::delete( array(
     821                        'id' => $id,
     822                ) );
     823        }
     824}
  • new file src/bp-core/classes/class-bp-optouts-list-table.php

    diff --git src/bp-core/classes/class-bp-optouts-list-table.php src/bp-core/classes/class-bp-optouts-list-table.php
    new file mode 100644
    index 000000000..0e9084245
    - +  
     1<?php
     2/**
     3 * BuddyPress Opt-outs List Table class.
     4 *
     5 * @package BuddyPress
     6 * @subpackage CoreAdminClasses
     7 * @since 8.0.0
     8 */
     9
     10// Exit if accessed directly.
     11defined( 'ABSPATH' ) || exit;
     12
     13/**
     14 * List table class for nonmember opt-outs admin page.
     15 *
     16 * @since 8.0.0
     17 */
     18class BP_Optouts_List_Table extends WP_Users_List_Table {
     19
     20        /**
     21         * Opt-out count.
     22         *
     23         * @since 8.0.0
     24         *
     25         * @var int
     26         */
     27        public $total_items = 0;
     28
     29        /**
     30         * Constructor.
     31         *
     32         * @since 8.0.0
     33         */
     34        public function __construct() {
     35                // Define singular and plural labels, as well as whether we support AJAX.
     36                parent::__construct(
     37                        array(
     38                                'ajax'     => false,
     39                                'plural'   => 'optouts',
     40                                'singular' => 'optout',
     41                                'screen'   => get_current_screen()->id,
     42                        )
     43                );
     44        }
     45
     46        /**
     47         * Set up items for display in the list table.
     48         *
     49         * Handles filtering of data, sorting, pagination, and any other data
     50         * manipulation required prior to rendering.
     51         *
     52         * @since 8.0.0
     53         */
     54        public function prepare_items() {
     55                global $usersearch;
     56
     57                $search   = isset( $_REQUEST['s'] ) ? $_REQUEST['s'] : '';
     58                $per_page = $this->get_items_per_page( str_replace( '-', '_', "{$this->screen->id}_per_page" ) );
     59                $paged    = $this->get_pagenum();
     60
     61                $args = array(
     62                        'search_terms' => $search,
     63                        'order_by'     => 'date_modified',
     64                        'sort_order'   => 'DESC',
     65                        'page'         => $paged,
     66                        'per_page'     => $per_page,
     67                );
     68
     69                if ( isset( $_REQUEST['orderby'] ) ) {
     70                        $args['order_by'] = $_REQUEST['orderby'];
     71                }
     72
     73                if ( isset( $_REQUEST['order'] ) ) {
     74                        $args['sort_order'] = $_REQUEST['order'];
     75                }
     76
     77                $this->items       = bp_get_optouts( $args );
     78                $optouts_class     = new BP_Optout();
     79                $this->total_items = $optouts_class->get_total_count( $args );
     80
     81                $this->set_pagination_args(
     82                        array(
     83                                'total_items' => $this->total_items,
     84                                'per_page'    => $per_page,
     85                        )
     86                );
     87        }
     88
     89        /**
     90         * Get the list of views available on this table.
     91         *
     92         * @since 8.0.0
     93         */
     94        public function views() {
     95                if ( is_multisite() && bp_core_do_network_admin() ) {
     96                        $tools_parent = 'admin.php';
     97                } else {
     98                        $tools_parent = 'tools.php';
     99                }
     100
     101                $url_base = add_query_arg(
     102                        array(
     103                                'page' => 'bp-optouts',
     104                        ),
     105                        bp_get_admin_url( $tools_parent )
     106                );
     107                ?>
     108
     109                <h2 class="screen-reader-text">
     110                        <?php
     111                                /* translators: accessibility text */
     112                                esc_html_e( 'Filter opt-outs list', 'buddypress' );
     113                        ?>
     114                </h2>
     115                <ul class="subsubsub">
     116                        <?php
     117                        /**
     118                         * Fires inside listing of views so plugins can add their own.
     119                         *
     120                         * @since 8.0.0
     121                         *
     122                         * @param string $url_base       Current URL base for view.
     123                         * @param array  $active_filters Current filters being requested.
     124                         */
     125                        do_action( 'bp_optouts_list_table_get_views', $url_base, $this->active_filters ); ?>
     126                </ul>
     127        <?php
     128        }
     129
     130        /**
     131         * Get rid of the extra nav.
     132         *
     133         * WP_Users_List_Table will add an extra nav to change user's role.
     134         * As we're dealing with opt-outs, we don't need this.
     135         *
     136         * @since 8.0.0
     137         *
     138         * @param array $which Current table nav item.
     139         */
     140        public function extra_tablenav( $which ) {
     141                return;
     142        }
     143
     144        /**
     145         * Specific opt-out columns.
     146         *
     147         * @since 8.0.0
     148         *
     149         * @return array
     150         */
     151        public function get_columns() {
     152                /**
     153                 * Filters the nonmember opt-outs columns.
     154                 *
     155                 * @since 8.0.0
     156                 *
     157                 * @param array $value Array of columns to display.
     158                 */
     159                return apply_filters(
     160                        'bp_optouts_list_columns',
     161                        array(
     162                                'cb'                     => '<input type="checkbox" />',
     163                                'email_address'          => __( 'Email Address Hash', 'buddypress' ),
     164                                'username'               => __( 'Email Sender', 'buddypress' ),
     165                                'user_registered'        => __( 'Email Sender Registered', 'buddypress' ),
     166                                'email_type'             => __( 'Email Type', 'buddypress' ),
     167                                'email_type_description' => __( 'Email Description', 'buddypress' ),
     168                                'optout_date_modified'   => __( 'Date Modified', 'buddypress' ),
     169                        )
     170                );
     171        }
     172
     173        /**
     174         * Specific bulk actions for opt-outs.
     175         *
     176         * @since 8.0.0
     177         */
     178        public function get_bulk_actions() {
     179                if ( current_user_can( 'delete_users' ) ) {
     180                        $actions['delete'] = _x( 'Delete', 'Optout database record action', 'buddypress' );
     181                }
     182
     183                return $actions;
     184        }
     185
     186        /**
     187         * The text shown when no items are found.
     188         *
     189         * Nice job, clean sheet!
     190         *
     191         * @since 8.0.0
     192         */
     193        public function no_items() {
     194                esc_html_e( 'No opt-outs found.', 'buddypress' );
     195        }
     196
     197        /**
     198         * The columns opt-outs can be reordered by.
     199         *
     200         * @since 8.0.0
     201         */
     202        public function get_sortable_columns() {
     203                return array(
     204                        'username'             => 'user_id',
     205                        'email_type'           => 'email_type',
     206                        'optout_date_modified' => 'date_modified',
     207                );
     208        }
     209
     210        /**
     211         * Display opt-out rows.
     212         *
     213         * @since 8.0.0
     214         */
     215        public function display_rows() {
     216                $style = '';
     217                foreach ( $this->items as $optout ) {
     218                        $style = ( ' class="alternate"' == $style ) ? '' : ' class="alternate"';
     219                        echo "\n\t" . $this->single_row( $optout, $style );
     220                }
     221        }
     222
     223        /**
     224         * Display an opt-out row.
     225         *
     226         * @since 8.0.0
     227         *
     228         * @see WP_List_Table::single_row() for explanation of params.
     229         *
     230         * @param BP_Optout $optout   BP_Optout object.
     231         * @param string    $style    Styles for the row.
     232         * @param string    $role     Role to be assigned to user.
     233         * @param int       $numposts Number of posts.
     234         * @return void
     235         */
     236        public function single_row( $optout = null, $style = '', $role = '', $numposts = 0 ) {
     237                echo '<tr' . $style . ' id="optout-' . intval( $optout->id ) . '">';
     238                echo $this->single_row_columns( $optout );
     239                echo '</tr>';
     240        }
     241
     242        /**
     243         * Markup for the checkbox used to select items for bulk actions.
     244         *
     245         * @since 8.0.0
     246         *
     247         * @param BP_Optout $optout BP_Optout object.
     248         */
     249        public function column_cb( $optout = null ) {
     250        ?>
     251                <label class="screen-reader-text" for="optout_<?php echo intval( $optout->id ); ?>">
     252                        <?php
     253                                /* translators: %d: accessibility text. */
     254                                printf( esc_html__( 'Select opt-out request: %d', 'buddypress' ), intval( $optout->id ) );
     255                        ?>
     256                </label>
     257                <input type="checkbox" id="optout_<?php echo intval( $optout->id ) ?>" name="optout_ids[]" value="<?php echo esc_attr( $optout->id ) ?>" />
     258                <?php
     259        }
     260
     261        /**
     262         * Markup for the checkbox used to select items for bulk actions.
     263         *
     264         * @since 8.0.0
     265         *
     266         * @param BP_Optout $optout BP_Optout object.
     267         */
     268        public function column_email_address( $optout = null ) {
     269                printf( '<a href="mailto:%1$s">%2$s</a>', esc_attr( $optout->email_address ), esc_html( $optout->email_address ) );
     270
     271                $actions = array();
     272
     273                if ( is_network_admin() ) {
     274                        $form_url = network_admin_url( 'admin.php' );
     275                } else {
     276                        $form_url = bp_get_admin_url( 'tools.php' );
     277                }
     278
     279                // Delete link.
     280                $delete_link = add_query_arg(
     281                        array(
     282                                'page'      => 'bp-optouts',
     283                                'optout_id' => $optout->id,
     284                                'action'    => 'delete',
     285                        ),
     286                        $form_url
     287                );
     288                $actions['delete'] = sprintf( '<a href="%1$s" class="delete">%2$s</a>', esc_url( $delete_link ), esc_html__( 'Delete', 'buddypress' ) );
     289
     290                /**
     291                 * Filters the row actions for each opt-out in list.
     292                 *
     293                 * @since 8.0.0
     294                 *
     295                 * @param array  $actions Array of actions and corresponding links.
     296                 * @param object $optout  The BP_Optout.
     297                 */
     298                $actions = apply_filters( 'bp_optouts_management_row_actions', $actions, $optout );
     299
     300                echo $this->row_actions( $actions );
     301        }
     302
     303        /**
     304         * The inviter/site member who sent the email that prompted the opt-out.
     305         *
     306         * @since 8.0.0
     307         *
     308         * @param BP_Optout $optout BP_Optout object.
     309         */
     310        public function column_username( $optout = null ) {
     311                $avatar = get_avatar( $optout->user_id, 32 );
     312                $inviter = get_user_by( 'id', $optout->user_id );
     313                if ( ! $inviter ) {
     314                        return;
     315                }
     316                $user_link = bp_core_get_user_domain( $optout->user_id );
     317                echo $avatar . sprintf( '<strong><a href="%1$s" class="edit">%2$s</a></strong><br/>', esc_url( $user_link ), esc_html( $inviter->user_login ) );
     318        }
     319
     320        /**
     321         * Display registration date of user whose communication prompted opt-out.
     322         *
     323         * @since 8.0.0
     324         *
     325         * @param BP_Optout $optout BP_Optout object.
     326         */
     327        public function column_user_registered( $optout = null ) {
     328                $inviter = get_user_by( 'id', $optout->user_id );
     329                if ( ! $inviter ) {
     330                        return;
     331                }
     332                echo esc_html( mysql2date( 'Y/m/d g:i:s a', $inviter->user_registered  ) );
     333        }
     334
     335        /**
     336         * Display type of email that prompted opt-out.
     337         *
     338         * @since 8.0.0
     339         *
     340         * @param BP_Optout $optout BP_Optout object.
     341         */
     342        public function column_email_type( $optout = null ) {
     343                echo esc_html( $optout->email_type );
     344        }
     345
     346        /**
     347         * Display description of bp-email-type that prompted opt-out.
     348         *
     349         * @since 8.0.0
     350         *
     351         * @param BP_Optout $optout BP_Optout object.
     352         */
     353        public function column_email_type_description( $optout = null ) {
     354                $type_term = get_term_by( 'slug', $optout->email_type, 'bp-email-type' );
     355                if ( $type_term ) {
     356                        echo esc_html( $type_term->description );
     357                }
     358
     359        }
     360
     361        /**
     362         * Display opt-out date.
     363         *
     364         * @since 8.0.0
     365         *
     366         * @param BP_Optout $optout BP_Optout object.
     367         */
     368        public function column_optout_date_modified( $optout = null ) {
     369                echo esc_html( mysql2date( 'Y/m/d g:i:s a', $optout->date_modified ) );
     370        }
     371
     372        /**
     373         * Allow plugins to add custom columns.
     374         *
     375         * @since 8.0.0
     376         *
     377         * @param BP_Optout $optout      BP_Optout object.
     378         * @param string    $column_name The column name.
     379         * @return string
     380         */
     381        function column_default( $optout = null, $column_name = '' ) {
     382
     383                /**
     384                 * Filters the single site custom columns for plugins.
     385                 *
     386                 * @since 8.0.0
     387                 *
     388                 * @param string    $column_name The column name.
     389                 * @param BP_Optout $optout      BP_Optout object.
     390                 */
     391                return apply_filters( 'bp_optouts_management_custom_column', '', $column_name, $optout );
     392        }
     393}
  • src/bp-members/classes/class-bp-members-admin.php

    diff --git src/bp-members/classes/class-bp-members-admin.php src/bp-members/classes/class-bp-members-admin.php
    index c999cb923..17f73cb37 100644
    class BP_Members_Admin { 
    488488                                'bp-signups',
    489489                                array( $this, 'signups_admin' )
    490490                        );
     491
    491492                }
    492493
    493494                $edit_page         = 'user-edit';
    class BP_Members_Admin { 
    509510                        $this->user_page    .= '-network';
    510511                        $this->users_page   .= '-network';
    511512                        $this->signups_page .= '-network';
     513
     514                        $this->members_optouts_page .= '-network';
    512515                }
    513516
    514517                // Setup the screen ID's.
    class BP_Members_Admin { 
    25642567
    25652568                return $value;
    25662569        }
     2570
    25672571}
    25682572endif; // End class_exists check.
  • src/bp-members/classes/class-bp-members-component.php

    diff --git src/bp-members/classes/class-bp-members-component.php src/bp-members/classes/class-bp-members-component.php
    index 9e6fbcd42..4db47da8d 100644
    class BP_Members_Component extends BP_Component { 
    178178                        'global_tables'   => array(
    179179                                'table_name_invitations'   => bp_core_get_table_prefix() . 'bp_invitations',
    180180                                'table_name_last_activity' => bp_core_get_table_prefix() . 'bp_activity',
     181                                'table_name_optouts'       => bp_core_get_table_prefix() . 'bp_optouts',
    181182                                'table_name_signups'       => $wpdb->base_prefix . 'signups', // Signups is a global WordPress table.
    182183                        )
    183184                );
  • src/class-buddypress.php

    diff --git src/class-buddypress.php src/class-buddypress.php
    index 3dd1d483c..1af41585b 100644
    class BuddyPress { 
    597597                        'BP_REST_Components_Endpoint'  => 'core',
    598598                        'BP_REST_Attachments'          => 'core',
    599599                        'BP_Admin_Types'               => 'core',
     600                        'BP_Optout'                    => 'core',
     601                        'BP_Optouts_List_Table'        => 'core',
    600602
    601603                        'BP_Core_Friends_Widget'   => 'friends',
    602604                        'BP_REST_Friends_Endpoint' => 'friends',
  • new file tests/phpunit/testcases/core/optouts.php

    diff --git tests/phpunit/testcases/core/optouts.php tests/phpunit/testcases/core/optouts.php
    new file mode 100644
    index 000000000..7c0d1ecc5
    - +  
     1<?php
     2/**
     3 * @group core
     4 * @group optouts
     5 */
     6 class BP_Tests_Optouts extends BP_UnitTestCase {
     7        public function test_bp_optouts_add_optout_vanilla() {
     8                $old_current_user = get_current_user_id();
     9
     10                $u1 = $this->factory->user->create();
     11                $this->set_current_user( $u1 );
     12
     13                // Create a couple of optouts.
     14                $args = array(
     15                        'email_address'     => 'one@wp.org',
     16                        'user_id'           => $u1,
     17                        'email_type'        => 'annoyance'
     18                );
     19                $i1 = bp_add_optout( $args );
     20                $args['email_address'] = 'two@wp.org';
     21                $i2 = bp_add_optout( $args );
     22
     23                $get_args = array(
     24                        'user_id'        => $u1,
     25                        'fields'         => 'ids',
     26                );
     27                $optouts = bp_get_optouts( $get_args );
     28                $this->assertEqualSets( array( $i1, $i2 ), $optouts );
     29
     30                $this->set_current_user( $old_current_user );
     31        }
     32
     33        public function test_bp_optouts_add_optout_avoid_duplicates() {
     34                $old_current_user = get_current_user_id();
     35
     36                $u1 = $this->factory->user->create();
     37                $this->set_current_user( $u1 );
     38
     39                // Create an optouts.
     40                $args = array(
     41                        'email_address'     => 'one@wp.org',
     42                        'user_id'           => $u1,
     43                        'email_type'        => 'annoyance'
     44                );
     45                $i1 = bp_add_optout( $args );
     46                // Attempt to create a duplicate. Should return existing optout id.
     47                $i2 = bp_add_optout( $args );
     48                $this->assertEquals( $i1, $i2 );
     49
     50                $this->set_current_user( $old_current_user );
     51        }
     52
     53        public function test_bp_optouts_delete_optout() {
     54                $old_current_user = get_current_user_id();
     55
     56                $u1 = $this->factory->user->create();
     57                $this->set_current_user( $u1 );
     58
     59                $args = array(
     60                        'email_address'     => 'one@wp.org',
     61                        'user_id'           => $u1,
     62                        'email_type'        => 'annoyance'
     63                );
     64                $i1 = bp_add_optout( $args );
     65                bp_delete_optout_by_id( $i1 );
     66
     67                $get_args = array(
     68                        'user_id'        => $u1,
     69                        'fields'         => 'ids',
     70                );
     71                $optouts = bp_get_optouts( $get_args );
     72                $this->assertTrue( empty( $optouts ) );
     73
     74                $this->set_current_user( $old_current_user );
     75        }
     76
     77        public function test_bp_optouts_get_by_search_terms() {
     78                $old_current_user = get_current_user_id();
     79
     80                $u1 = $this->factory->user->create();
     81                $this->set_current_user( $u1 );
     82
     83                // Create a couple of optouts.
     84                $args = array(
     85                        'email_address'     => 'one@wpfrost.org',
     86                        'user_id'           => $u1,
     87                        'email_type'        => 'annoyance'
     88                );
     89                $i1 = bp_add_optout( $args );
     90                $args['email_address'] = 'two@wp.org';
     91                $i2 = bp_add_optout( $args );
     92
     93                $get_args = array(
     94                        'search_terms'   => 'one@wpfrost.org',
     95                        'fields'         => 'ids',
     96                );
     97                $optouts = bp_get_optouts( $get_args );
     98                $this->assertEqualSets( array( $i1 ), $optouts );
     99
     100                $this->set_current_user( $old_current_user );
     101        }
     102
     103}