Skip to:
Content

BuddyPress.org

Ticket #8448: 8448.3.patch

File 8448.3.patch, 59.1 KB (added by imath, 2 years ago)
  • 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 c9a5259e9..26f549d8f 100644
    function bp_core_modify_admin_menu_highlight() { 
    8181        }
    8282
    8383        // Network Admin > Tools.
    84         if ( in_array( $plugin_page, array( 'bp-tools', 'available-tools' ) ) ) {
     84        if ( in_array( $plugin_page, array( 'bp-tools', 'available-tools', 'bp-optouts' ) ) ) {
    8585                $submenu_file = $plugin_page;
    8686        }
    8787}
  • 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..a80b188e4
    - +  
     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                get_current_screen()->add_help_tab( array(
     59                        'id'      => 'bp-optouts-overview',
     60                        'title'   => __( 'Overview', 'buddypress' ),
     61                        'content' =>
     62                        '<p>' . __( 'This is the administration screen for nonmember opt-outs on your site.', 'buddypress' ) . '</p>' .
     63                        '<p>' . __( 'From the screen options, you can customize the displayed columns and the pagination of this screen.', 'buddypress' ) . '</p>' .
     64                        '<p>' . __( 'You can reorder the list of opt-outs by clicking on the Email Address, User Who Contacted, Email Type or Date Modified column headers.', 'buddypress' ) . '</p>' .
     65                        '<p>' . __( 'Using the search form, you can search for an opt-out to a specific email address.', 'buddypress' ) . '</p>'
     66                ) );
     67
     68                get_current_screen()->add_help_tab( array(
     69                        'id'      => 'bp-optouts-actions',
     70                        'title'   => __( 'Actions', 'buddypress' ),
     71                        'content' =>
     72                        '<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>' .
     73                        '<ul><li>' . __( '"Delete" allows you to delete the record of an opt-out. You will be asked to confirm this deletion.', 'buddypress' ) . '</li></ul>' .
     74                        '<p>' . __( 'Bulk actions allow you to perform these actions for the selected rows.', 'buddypress' ) . '</p>'
     75                ) );
     76
     77                // Help panel - sidebar links.
     78                get_current_screen()->set_help_sidebar(
     79                        '<p><strong>' . __( 'For more information:', 'buddypress' ) . '</strong></p>' .
     80                        '<p>' . __( '<a href="https://buddypress.org/support/">Support Forums</a>', 'buddypress' ) . '</p>'
     81                );
     82
     83                // Add accessible hidden headings and text for the Pending Users screen.
     84                get_current_screen()->set_screen_reader_content( array(
     85                        /* translators: accessibility text */
     86                        'heading_views'      => __( 'Filter opt-outs list', 'buddypress' ),
     87                        /* translators: accessibility text */
     88                        'heading_pagination' => __( 'Opt-out list navigation', 'buddypress' ),
     89                        /* translators: accessibility text */
     90                        'heading_list'       => __( 'Opt-outs list', 'buddypress' ),
     91                ) );
     92
     93        } else {
     94                if ( empty( $_REQUEST['optout_ids' ] ) ) {
     95                        return;
     96                }
     97                $optout_ids = wp_parse_id_list( $_REQUEST['optout_ids' ] );
     98
     99                // Handle optout deletion.
     100                if ( 'do_delete' == $doaction ) {
     101
     102                        // Nonce check.
     103                        check_admin_referer( 'optouts_delete' );
     104
     105                        $success = 0;
     106                        foreach ( $optout_ids as $optout_id ) {
     107                                if ( bp_delete_optout_by_id( $optout_id ) ) {
     108                                        $success++;
     109                                }
     110                        }
     111
     112                        $query_arg = array( 'updated' => 'deleted' );
     113
     114                        if ( ! empty( $success ) ) {
     115                                $query_arg['deleted'] = $success;
     116                        }
     117
     118                        $notdeleted = count( $optout_ids ) - $success;
     119                        if ( $notdeleted > 0 ) {
     120                                $query_arg['notdeleted'] = $notdeleted;
     121                        }
     122
     123                        $redirect_to = add_query_arg( $query_arg, $redirect_to );
     124
     125                        bp_core_redirect( $redirect_to );
     126
     127                // Plugins can update other stuff from here.
     128                } else {
     129
     130                        /**
     131                         * Fires at end of opt-outs admin load
     132                         * if doaction does not match any actions.
     133                         *
     134                         * @since 2.0.0
     135                         *
     136                         * @param string $doaction Current bulk action being processed.
     137                         * @param array  $_REQUEST Current $_REQUEST global.
     138                         * @param string $redirect Determined redirect url to send user to.
     139                         */
     140                        do_action( 'bp_core_admin_update_optouts', $doaction, $_REQUEST, $redirect_to );
     141
     142                        bp_core_redirect( $redirect_to );
     143                }
     144        }
     145}
     146add_action( "load-tools_page_bp-optouts", 'bp_core_optouts_admin_load' );
     147
     148/**
     149 * Get admin notice when viewing the optouts management page.
     150 *
     151 * @since 8.0.0
     152 *
     153 * @return array
     154 */
     155function bp_core_get_optouts_notice() {
     156
     157        // Setup empty notice for return value.
     158        $notice = array();
     159
     160        // Updates.
     161        if ( ! empty( $_REQUEST['updated'] ) && 'deleted' === $_REQUEST['updated'] ) {
     162                $notice = array(
     163                        'class'   => 'updated',
     164                        'message' => ''
     165                );
     166
     167                if ( ! empty( $_REQUEST['deleted'] ) ) {
     168                        $notice['message'] .= sprintf(
     169                                /* translators: %s: number of deleted optouts */
     170                                _nx( '%s optout successfully deleted!', '%s optouts successfully deleted!',
     171                                 absint( $_REQUEST['deleted'] ),
     172                                 'nonmembers optout deleted',
     173                                 'buddypress'
     174                                ),
     175                                number_format_i18n( absint( $_REQUEST['deleted'] ) )
     176                        );
     177                }
     178
     179                if ( ! empty( $_REQUEST['notdeleted'] ) ) {
     180                        $notice['message'] .= sprintf(
     181                                /* translators: %s: number of optouts that failed to be deleted */
     182                                _nx( '%s optout was not deleted.', '%s optouts were not deleted.',
     183                                 absint( $_REQUEST['notdeleted'] ),
     184                                 'nonmembers optout not deleted',
     185                                 'buddypress'
     186                                ),
     187                                number_format_i18n( absint( $_REQUEST['notdeleted'] ) )
     188                        );
     189
     190                        if ( empty( $_REQUEST['deleted'] ) ) {
     191                                $notice['class'] = 'error';
     192                        }
     193                }
     194        }
     195
     196        // Errors.
     197        if ( ! empty( $_REQUEST['error'] ) && 'do_delete' === $_REQUEST['error'] ) {
     198                $notice = array(
     199                        'class'   => 'error',
     200                        'message' => esc_html__( 'There was a problem deleting optouts. Please try again.', 'buddypress' ),
     201                );
     202        }
     203
     204        return $notice;
     205}
     206
     207/**
     208 * Opt-outs admin page router.
     209 *
     210 * Depending on the context, display
     211 * - the list of optouts,
     212 * - or the delete confirmation screen,
     213 *
     214 * Also prepare the admin notices.
     215 *
     216 * @since 8.0.0
     217 */
     218function bp_core_optouts_admin() {
     219        $doaction = bp_admin_list_table_current_bulk_action();
     220
     221        // Prepare notices for admin.
     222        $notice = bp_core_get_optouts_notice();
     223
     224        // Display notices.
     225        if ( ! empty( $notice ) ) :
     226                if ( 'updated' === $notice['class'] ) : ?>
     227
     228                        <div id="message" class="<?php echo esc_attr( $notice['class'] ); ?> notice is-dismissible">
     229
     230                <?php else: ?>
     231
     232                        <div class="<?php echo esc_attr( $notice['class'] ); ?> notice is-dismissible">
     233
     234                <?php endif; ?>
     235
     236                        <p><?php echo $notice['message']; ?></p>
     237                </div>
     238
     239        <?php endif;
     240
     241        // Show the proper screen.
     242        switch ( $doaction ) {
     243                case 'delete' :
     244                        bp_core_optouts_admin_manage( $doaction );
     245                        break;
     246
     247                default:
     248                        bp_core_optouts_admin_index();
     249                        break;
     250        }
     251}
     252
     253/**
     254 * This is the list of optouts.
     255 *
     256 * @since 8.0.0
     257 *
     258 * @global $plugin_page
     259 * @global $bp_optouts_list_table
     260 */
     261function bp_core_optouts_admin_index() {
     262        global $plugin_page, $bp_optouts_list_table;
     263
     264        $usersearch = ! empty( $_REQUEST['s'] ) ? stripslashes( $_REQUEST['s'] ) : '';
     265
     266        // Prepare the group items for display.
     267        $bp_optouts_list_table->prepare_items();
     268
     269        if ( is_network_admin() ) {
     270                $form_url = network_admin_url( 'network-tools' );
     271        } else {
     272                $form_url = bp_get_admin_url( 'tools.php' );
     273        }
     274
     275        $form_url = add_query_arg(
     276                array(
     277                        'page' => 'bp-optouts',
     278                ),
     279                $form_url
     280        );
     281
     282        $search_form_url = remove_query_arg(
     283                array(
     284                        'action',
     285                        'deleted',
     286                        'notdeleted',
     287                        'error',
     288                        'updated',
     289                        'delete',
     290                        'activate',
     291                        'activated',
     292                        'notactivated',
     293                        'resend',
     294                        'resent',
     295                        'notresent',
     296                        'do_delete',
     297                        'do_activate',
     298                        'do_resend',
     299                        'action2',
     300                        '_wpnonce',
     301                        'optout_ids'
     302                ), $_SERVER['REQUEST_URI']
     303        );
     304
     305        ?>
     306
     307        <div class="wrap">
     308                <h1 class="wp-heading-inline"><?php _e( 'Nonmember Opt-outs', 'buddypress' ); ?></h1>
     309
     310                <?php
     311                if ( $usersearch ) {
     312                        printf( '<span class="subtitle">' . __( 'Opt-outs with an email address matching &#8220;%s&#8221;', 'buddypress' ) . '</span>', esc_html( $usersearch ) );
     313                }
     314                ?>
     315                <p class="description"><?php _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>
     316
     317                <hr class="wp-header-end">
     318
     319                <?php // Display each opt-out on its own row. ?>
     320                <?php $bp_optouts_list_table->views(); ?>
     321
     322                <form id="bp-optouts-search-form" action="<?php echo esc_url( $search_form_url ) ;?>">
     323                        <input type="hidden" name="page" value="<?php echo esc_attr( $plugin_page ); ?>" />
     324                        <?php $bp_optouts_list_table->search_box( __( 'Search for a specific email address', 'buddypress' ), 'bp-optouts' ); ?>
     325                </form>
     326
     327                <form id="bp-optouts-form" action="<?php echo esc_url( $form_url );?>" method="post">
     328                        <?php $bp_optouts_list_table->display(); ?>
     329                </form>
     330        </div>
     331<?php
     332}
     333
     334/**
     335 * This is the confirmation screen for actions.
     336 *
     337 * @since 8.0.0
     338 *
     339 * @param string $action Delete or resend optout.
     340 *
     341 * @return null|false
     342 */
     343function bp_core_optouts_admin_manage( $action = '' ) {
     344        $capability = bp_core_do_network_admin() ? 'manage_network_options' : 'manage_options';
     345        if ( ! current_user_can( $capability ) || empty( $action ) ) {
     346                die( '-1' );
     347        }
     348
     349        // Get the IDs from the URL.
     350        $ids = false;
     351        if ( ! empty( $_POST['optout_ids'] ) ) {
     352                $ids = wp_parse_id_list( $_POST['optout_ids'] );
     353        } elseif ( ! empty( $_GET['optout_id'] ) ) {
     354                $ids = absint( $_GET['optout_id'] );
     355        }
     356
     357        if ( empty( $ids ) ) {
     358                return false;
     359        }
     360
     361        // Query for matching optouts, and filter out bad IDs.
     362        $args = array(
     363                'id'     => $ids,
     364        );
     365        $optouts    = bp_get_optouts( $args );
     366        $optout_ids = wp_list_pluck( $optouts, 'id' );
     367
     368        // Check optout IDs and set up strings.
     369        switch ( $action ) {
     370                case 'delete' :
     371                        $header_text = __( 'Delete optouts', 'buddypress' );
     372                        if ( 1 == count( $optouts ) ) {
     373                                $helper_text = __( 'You are about to delete the following opt-out request:', 'buddypress' );
     374                        } else {
     375                                $helper_text = __( 'You are about to delete the following opt-out requests:', 'buddypress' );
     376                        }
     377                        break;
     378        }
     379
     380        // These arguments are added to all URLs.
     381        $url_args = array( 'page' => 'bp-optouts' );
     382
     383        // These arguments are only added when performing an action.
     384        $action_args = array(
     385                'action'     => 'do_' . $action,
     386                'optout_ids' => implode( ',', $optout_ids )
     387        );
     388
     389        if ( is_network_admin() ) {
     390                $base_url = network_admin_url( 'network-tools' );
     391        } else {
     392                $base_url = bp_get_admin_url( 'tools.php' );
     393        }
     394
     395        $cancel_url = add_query_arg( $url_args, $base_url );
     396        $action_url = wp_nonce_url(
     397                add_query_arg(
     398                        array_merge( $url_args, $action_args ),
     399                        $base_url
     400                ),
     401                'optouts_' . $action
     402        );
     403
     404        ?>
     405
     406        <div class="wrap">
     407                <h1 class="wp-heading-inline"><?php echo esc_html( $header_text ); ?></h1>
     408                <hr class="wp-header-end">
     409
     410                <p><?php echo esc_html( $helper_text ); ?></p>
     411
     412                <ol class="bp-optouts-list">
     413                <?php foreach ( $optouts as $optout ) : ?>
     414
     415                        <li>
     416                                <strong><?php echo esc_html( $optout->email_address ) ?></strong>
     417                                <p class="description">
     418                                        <?php
     419                                        $last_modified = mysql2date( 'Y/m/d g:i:s a', $optout->date_modified );
     420                                        /* translators: %s: modification date */
     421                                        printf( esc_html__( 'Date modified: %s', 'buddypress'), $last_modified );
     422                                        ?>
     423                                </p>
     424                        </li>
     425
     426                <?php endforeach; ?>
     427                </ol>
     428
     429                <?php if ( 'delete' === $action ) : ?>
     430
     431                        <p><strong><?php esc_html_e( 'This action cannot be undone.', 'buddypress' ) ?></strong></p>
     432
     433                <?php endif ; ?>
     434
     435                <a class="button-primary" href="<?php echo esc_url( $action_url ); ?>"><?php esc_html_e( 'Confirm', 'buddypress' ); ?></a>
     436                <a class="button" href="<?php echo esc_url( $cancel_url ); ?>"><?php esc_html_e( 'Cancel', 'buddypress' ) ?></a>
     437        </div>
     438
     439        <?php
     440}
  • 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 d7cab5bbe..ac49a63ae 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() { 
    589592         */
    590593        do_action( 'bp_core_install_invitations' );
    591594}
     595
     596/**
     597 * Install database tables to store opt-out requests from nonmembers.
     598 *
     599 * @since 8.0.0
     600 *
     601 * @uses bp_core_set_charset()
     602 * @uses bp_core_get_table_prefix()
     603 * @uses dbDelta()
     604 */
     605function bp_core_install_nonmember_opt_outs() {
     606        $sql             = array();
     607        $charset_collate = $GLOBALS['wpdb']->get_charset_collate();
     608        $bp_prefix       = bp_core_get_table_prefix();
     609        $optouts_class   = new BP_Optout();
     610        $table_name      = $optouts_class->get_table_name();
     611        $sql = "CREATE TABLE {$table_name} (
     612                id bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY,
     613                email_address_hash varchar(255) NOT NULL,
     614                user_id bigint(20) NOT NULL,
     615                email_type varchar(255) NOT NULL,
     616                date_modified datetime NOT NULL,
     617                KEY user_id (user_id),
     618                KEY email_type (email_type),
     619                KEY date_modified (date_modified)
     620                ) {$charset_collate};";
     621        dbDelta( $sql );
     622
     623        /**
     624         * Fires after BuddyPress adds the nonmember opt-outs table.
     625         *
     626         * @since 8.0.0
     627         */
     628        do_action( 'bp_core_install_nonmember_opt_outs' );
     629}
  • 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 592f5b155..88e4b651b 100644
    function bp_get_widget_max_count_limit( $widget_class = '' ) { 
    42324232         */
    42334233        return apply_filters( 'bp_get_widget_max_count_limit', 50, $widget_class );
    42344234}
     4235
     4236/**
     4237 * Add a new BP_Optout.
     4238 *
     4239 * @since 8.0.0
     4240 *
     4241 * @param array $args {
     4242 *     An array of arguments describing the new opt-out.
     4243 *     @type string $email_address Email address of user who has opted out.
     4244 *     @type int    $user_id       Optional. ID of user whose communication
     4245 *                                 prompted the user to opt-out.
     4246 *     @type string $email_type    Optional. Name of the email type that
     4247 *                                 prompted the user to opt-out.
     4248 *     @type string $date_modified Optional. Specify a time, else now will be used.
     4249 * }
     4250 * @return false | int False on failure, ID of new (or existing) opt-out if successful.
     4251 */
     4252function bp_add_optout( $args = array() ) {
     4253        $optout = new BP_Optout();
     4254        $r = bp_parse_args( $args, array(
     4255                'email_address'     => '',
     4256                'user_id'           => 0,
     4257                'email_type'        => '',
     4258                'date_modified'     => bp_core_current_time(),
     4259        ), 'add_optout' );
     4260
     4261        // Opt-outs must have an email address.
     4262        if ( empty( $r['email_address'] ) ) {
     4263                return false;
     4264        }
     4265
     4266        // Avoid creating duplicate opt-outs.
     4267        $optout_id = $optout->optout_exists( array(
     4268                'email_address' => $r['email_address'],
     4269                'user_id'       => $r['user_id'],
     4270                'email_type'    => $r['email_type'],
     4271        ) );
     4272
     4273        if ( ! $optout_id ) {
     4274                // Set up the new opt-out.
     4275                $optout->email_address = $r['email_address'];
     4276                $optout->user_id       = $r['user_id'];
     4277                $optout->email_type    = $r['email_type'];
     4278                $optout->date_modified = $r['date_modified'];
     4279
     4280                $optout_id = $optout->save();
     4281        }
     4282
     4283        return $optout_id;
     4284}
     4285
     4286/**
     4287 * Find matching BP_Optouts.
     4288 *
     4289 * @since 8.0.0
     4290 *
     4291 * @see BP_Optout::get() for a description of parameters and return values.
     4292 */
     4293function bp_get_optouts( $args = array() ) {
     4294        $optout_class = new BP_Optout();
     4295        return $optout_class::get( $args );
     4296}
     4297
     4298/**
     4299 * Delete a BP_Optout by ID.
     4300 *
     4301 * @since 8.0.0
     4302 *
     4303 * @param int $id ID of the optout to delete.
     4304 */
     4305function bp_delete_optout_by_id( $id = 0 ) {
     4306        $optout_class = new BP_Optout();
     4307        return $optout_class::delete_by_id( $id );
     4308}
  • src/bp-core/bp-core-update.php

    diff --git src/bp-core/bp-core-update.php src/bp-core/bp-core-update.php
    index a7791c445..fe6a26d9e 100644
    function bp_update_to_8_0() { 
    621621                        '%s',
    622622                )
    623623        );
     624
     625        bp_core_install_nonmember_opt_outs();
    624626}
    625627
    626628/**
  • 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 32dd0768d..2edd975c5 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();
  • 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..6d7725c34
    - +  
     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 optout 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( $args, array(
     534                        'id'                => false,
     535                        'email_address'     => false,
     536                        'user_id'           => false,
     537                        'email_type'        => false,
     538                        'search_terms'      => '',
     539                        'order_by'          => false,
     540                        'sort_order'        => false,
     541                        'page'              => false,
     542                        'per_page'          => false,
     543                        'fields'            => 'all',
     544                ), 'bp_optout_get' );
     545
     546                $sql = array(
     547                        'select'     => "SELECT",
     548                        'fields'     => '',
     549                        'from'       => "FROM {$optouts_table_name} o",
     550                        'where'      => '',
     551                        'orderby'    => '',
     552                        'pagination' => '',
     553                );
     554
     555                if ( 'user_ids' === $r['fields'] ) {
     556                        $sql['fields'] = "DISTINCT o.user_id";
     557                } else if ( 'email_addresses' === $r['fields'] ) {
     558                        $sql['fields'] = "DISTINCT o.email_address_hash";
     559                } else {
     560                        $sql['fields'] = 'DISTINCT o.id';
     561                }
     562
     563                // WHERE.
     564                $sql['where'] = self::get_where_sql( array(
     565                        'id'            => $r['id'],
     566                        'email_address' => $r['email_address'],
     567                        'user_id'       => $r['user_id'],
     568                        'email_type'    => $r['email_type'],
     569                        'search_terms'  => $r['search_terms'],
     570                ) );
     571
     572                // ORDER BY.
     573                $sql['orderby'] = self::get_order_by_sql( array(
     574                        'order_by'   => $r['order_by'],
     575                        'sort_order' => $r['sort_order']
     576                ) );
     577
     578                // LIMIT %d, %d.
     579                $sql['pagination'] = self::get_paged_sql( array(
     580                        'page'     => $r['page'],
     581                        'per_page' => $r['per_page'],
     582                ) );
     583
     584                $paged_optouts_sql = "{$sql['select']} {$sql['fields']} {$sql['from']} {$sql['where']} {$sql['orderby']} {$sql['pagination']}";
     585
     586                /**
     587                 * Filters the pagination SQL statement.
     588                 *
     589                 * @since 8.0.0
     590                 *
     591                 * @param string $value Concatenated SQL statement.
     592                 * @param array  $sql   Array of SQL parts before concatenation.
     593                 * @param array  $r     Array of parsed arguments for the get method.
     594                 */
     595                $paged_optouts_sql = apply_filters( 'bp_optouts_get_paged_optouts_sql', $paged_optouts_sql, $sql, $r );
     596
     597                $cached = bp_core_get_incremented_cache( $paged_optouts_sql, 'bp_optouts' );
     598                if ( false === $cached ) {
     599                        $paged_optout_ids = $wpdb->get_col( $paged_optouts_sql );
     600                        bp_core_set_incremented_cache( $paged_optouts_sql, 'bp_optouts', $paged_optout_ids );
     601                } else {
     602                        $paged_optout_ids = $cached;
     603                }
     604
     605                // Special return format cases.
     606                if ( in_array( $r['fields'], array( 'ids', 'user_ids' ), true ) ) {
     607                        // We only want the field that was found.
     608                        return array_map( 'intval', $paged_optout_ids );
     609                } else if ( 'email_addresses' === $r['fields'] ) {
     610                        return $paged_optout_ids;
     611                }
     612
     613                $uncached_ids = bp_get_non_cached_ids( $paged_optout_ids, 'bp_optouts' );
     614                if ( $uncached_ids ) {
     615                        $ids_sql = implode( ',', array_map( 'intval', $uncached_ids ) );
     616                        $data_objects = $wpdb->get_results( "SELECT o.* FROM {$optouts_table_name} o WHERE o.id IN ({$ids_sql})" );
     617                        foreach ( $data_objects as $data_object ) {
     618                                wp_cache_set( $data_object->id, $data_object, 'bp_optouts' );
     619                        }
     620                }
     621
     622                $paged_optouts = array();
     623                foreach ( $paged_optout_ids as $paged_optout_id ) {
     624                        $paged_optouts[] = new BP_optout( $paged_optout_id );
     625                }
     626
     627                return $paged_optouts;
     628        }
     629
     630        /**
     631         * Get a count of total optouts matching a set of arguments.
     632         *
     633         * @since 8.0.0
     634         *
     635         * @see BP_optout::get() for a description of
     636         *      arguments.
     637         *
     638         * @param array $args See {@link BP_optout::get()}.
     639         * @return int Count of located items.
     640         */
     641        public static function get_total_count( $args ) {
     642                global $wpdb;
     643                $optouts_table_name = BP_Optout::get_table_name();
     644
     645                // Parse the arguments.
     646                $r  = bp_parse_args( $args, array(
     647                        'id'                => false,
     648                        'email_address'     => false,
     649                        'user_id'           => false,
     650                        'email_type'        => false,
     651                        'search_terms'      => '',
     652                        'order_by'          => false,
     653                        'sort_order'        => false,
     654                        'page'              => false,
     655                        'per_page'          => false,
     656                        'fields'            => 'all',
     657                ), 'bp_optout_get_total_count' );
     658
     659                // Build the query
     660                $select_sql = "SELECT COUNT(*)";
     661                $from_sql   = "FROM {$optouts_table_name}";
     662                $where_sql  = self::get_where_sql( $r );
     663                $sql        = "{$select_sql} {$from_sql} {$where_sql}";
     664
     665                // Return the queried results
     666                return $wpdb->get_var( $sql );
     667        }
     668
     669        /**
     670         * Update optouts.
     671         *
     672         * @since 8.0.0
     673         *
     674         * @see BP_optout::get() for a description of
     675         *      accepted update/where arguments.
     676         *
     677         * @param array $update_args Associative array of fields to update,
     678         *                           and the values to update them to. Of the format
     679         *                           array( 'user_id' => 4, 'email_address' => 'bar@foo.com', ).
     680         * @param array $where_args  Associative array of columns/values, to
     681         *                           determine which rows should be updated. Of the format
     682         *                           array( 'user_id' => 7, 'email_address' => 'bar@foo.com', ).
     683         * @return int|bool Number of rows updated on success, false on failure.
     684         */
     685        public static function update( $update_args = array(), $where_args = array() ) {
     686                $update = self::get_query_clauses( $update_args );
     687                $where  = self::get_query_clauses( $where_args  );
     688
     689                /**
     690                 * Fires before an opt-out is updated.
     691                 *
     692                 * @since 8.0.0
     693                 *
     694                 * @param array $where_args  Associative array of columns/values describing
     695                 *                           optouts about to be deleted.
     696                 * @param array $update_args Array of new values.
     697                 */
     698                do_action( 'bp_optout_before_update', $where_args, $update_args );
     699
     700                $retval = self::_update( $update['data'], $where['data'], $update['format'], $where['format'] );
     701
     702                // Clear matching items from the cache.
     703                $cache_args = $where_args;
     704                $cache_args['fields'] = 'ids';
     705                $maybe_cached_ids = self::get( $cache_args );
     706                foreach ( $maybe_cached_ids as $invite_id ) {
     707                        wp_cache_delete( $invite_id, 'bp_optouts' );
     708                }
     709
     710                /**
     711                 * Fires after an opt-out is updated.
     712                 *
     713                 * @since 8.0.0
     714                 *
     715                 * @param array $where_args  Associative array of columns/values describing
     716                 *                           optouts about to be deleted.
     717                 * @param array $update_args Array of new values.
     718                 */
     719                do_action( 'bp_optout_after_update', $where_args, $update_args );
     720
     721                return $retval;
     722        }
     723
     724        /**
     725         * Delete opt-outs.
     726         *
     727         * @since 8.0.0
     728         *
     729         * @see BP_optout::get() for a description of
     730         *      accepted where arguments.
     731         *
     732         * @param array $args Associative array of columns/values, to determine
     733         *                    which rows should be deleted.  Of the format
     734         *                    array( 'user_id' => 7, 'email_address' => 'bar@foo.com', ).
     735         * @return int|bool Number of rows deleted on success, false on failure.
     736         */
     737        public static function delete( $args = array() ) {
     738                $where = self::get_query_clauses( $args );
     739
     740                /**
     741                 * Fires before an optout is deleted.
     742                 *
     743                 * @since 8.0.0
     744                 *
     745                 * @param array $args Characteristics of the optouts to be deleted.
     746                 */
     747                do_action( 'bp_optout_before_delete', $args );
     748
     749                // Clear matching items from the cache.
     750                $cache_args = $args;
     751                $cache_args['fields'] = 'ids';
     752                $maybe_cached_ids = self::get( $cache_args );
     753                foreach ( $maybe_cached_ids as $invite_id ) {
     754                        wp_cache_delete( $invite_id, 'bp_optouts' );
     755                }
     756
     757                $retval = self::_delete( $where['data'], $where['format'] );
     758
     759                /**
     760                 * Fires after an optout is deleted.
     761                 *
     762                 * @since 8.0.0
     763                 *
     764                 * @param array $args Characteristics of the optouts just deleted.
     765                 */
     766                do_action( 'bp_optout_after_delete', $args );
     767
     768                return $retval;
     769        }
     770
     771        /** Convenience methods ***********************************************/
     772
     773        /**
     774         * Check whether an invitation exists matching the passed arguments.
     775         *
     776         * @since 5.0.0
     777         *
     778         * @see BP_Optout::get() for a description of accepted parameters.
     779         *
     780         * @return int|bool ID of first found invitation or false if none found.
     781         */
     782        public function optout_exists( $args = array() ) {
     783                $exists = false;
     784
     785                $args['fields'] = 'ids';
     786                $optouts = BP_Optout::get( $args );
     787                if ( $optouts ) {
     788                        $exists = current( $optouts );
     789                }
     790                return $exists;
     791        }
     792
     793        /**
     794         * Delete a single optout by ID.
     795         *
     796         * @since 8.0.0
     797         *
     798         * @see BP_optout::delete() for explanation of
     799         *      return value.
     800         *
     801         * @param int $id ID of the optout item to be deleted.
     802         * @return bool True on success, false on failure.
     803         */
     804        public static function delete_by_id( $id ) {
     805                return self::delete( array(
     806                        'id' => $id,
     807                ) );
     808        }
     809
     810}
  • 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..7615583d8
    - +  
     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( array(
     37                        'ajax'     => false,
     38                        'plural'   => 'optouts',
     39                        'singular' => 'optout',
     40                        'screen'   => get_current_screen()->id,
     41                ) );
     42        }
     43
     44        /**
     45         * Set up items for display in the list table.
     46         *
     47         * Handles filtering of data, sorting, pagination, and any other data
     48         * manipulation required prior to rendering.
     49         *
     50         * @since 8.0.0
     51         */
     52        public function prepare_items() {
     53                global $usersearch;
     54
     55                $search   = isset( $_REQUEST['s'] ) ? $_REQUEST['s'] : '';
     56                $per_page = $this->get_items_per_page( str_replace( '-', '_', "{$this->screen->id}_per_page" ) );
     57                $paged    = $this->get_pagenum();
     58
     59                $args = array(
     60                        'search_terms'      => $search,
     61                        'order_by'          => 'date_modified',
     62                        'sort_order'        => 'DESC',
     63                        'page'              => $paged,
     64                        'per_page'          => $per_page,
     65                );
     66
     67                if ( isset( $_REQUEST['orderby'] ) ) {
     68                        $args['order_by'] = $_REQUEST['orderby'];
     69                }
     70
     71                if ( isset( $_REQUEST['order'] ) ) {
     72                        $args['sort_order'] = $_REQUEST['order'];
     73                }
     74
     75                $this->items       = bp_get_optouts( $args );
     76                $optouts_class     = new BP_Optout();
     77                $this->total_items = $optouts_class->get_total_count( $args );
     78
     79                $this->set_pagination_args( array(
     80                        'total_items' => $this->total_items,
     81                        'per_page'    => $per_page,
     82                ) );
     83        }
     84
     85        /**
     86         * Get the list of views available on this table.
     87         *
     88         * @since 8.0.0
     89         */
     90        public function views() {
     91                if ( is_multisite() && bp_core_do_network_admin() ) {
     92                        $tools_parent = 'network-tools';
     93                } else {
     94                        $tools_parent = 'tools.php';
     95                }
     96
     97                $url_base = add_query_arg(
     98                        array(
     99                                'page' => 'bp-optouts',
     100                        ),
     101                        bp_get_admin_url( $tools_parent )
     102                );
     103                ?>
     104
     105                <h2 class="screen-reader-text"><?php
     106                        /* translators: accessibility text */
     107                        _e( 'Filter opt-outs list', 'buddypress' );
     108                ?></h2>
     109                <ul class="subsubsub">
     110                        <?php
     111                        /**
     112                         * Fires inside listing of views so plugins can add their own.
     113                         *
     114                         * @since 8.0.0
     115                         *
     116                         * @param string $url_base       Current URL base for view.
     117                         * @param array  $active_filters Current filters being requested.
     118                         */
     119                        do_action( 'bp_optouts_list_table_get_views', $url_base, $this->active_filters ); ?>
     120                </ul>
     121        <?php
     122        }
     123
     124        /**
     125         * Get rid of the extra nav.
     126         *
     127         * WP_Users_List_Table will add an extra nav to change user's role.
     128         * As we're dealing with opt-outs, we don't need this.
     129         *
     130         * @since 8.0.0
     131         *
     132         * @param array $which Current table nav item.
     133         */
     134        public function extra_tablenav( $which ) {
     135                return;
     136        }
     137
     138        /**
     139         * Specific opt-out columns.
     140         *
     141         * @since 8.0.0
     142         *
     143         * @return array
     144         */
     145        public function get_columns() {
     146                /**
     147                 * Filters the nonmember opt-outs columns.
     148                 *
     149                 * @since 8.0.0
     150                 *
     151                 * @param array $value Array of columns to display.
     152                 */
     153                return apply_filters( 'bp_optouts_list_columns', array(
     154                        'cb'                       => '<input type="checkbox" />',
     155                        'email_address'            => __( 'Email Address Hash',    'buddypress' ),
     156                        'username'                 => __( 'Email Sender',        'buddypress' ),
     157                        'user_registered'          => __( 'Email Sender Registered',        'buddypress' ),
     158                        'email_type'               => __( 'Email Type', 'buddypress' ),
     159                        'email_type_description'   => __( 'Email Description', 'buddypress' ),
     160                        'optout_date_modified'     => __( 'Date Modified',   'buddypress' ),
     161                ) );
     162        }
     163
     164        /**
     165         * Specific bulk actions for opt-outs.
     166         *
     167         * @since 8.0.0
     168         */
     169        public function get_bulk_actions() {
     170                if ( current_user_can( 'delete_users' ) ) {
     171                        $actions['delete'] = _x( 'Delete', 'Optout database record action', 'buddypress' );
     172                }
     173
     174                return $actions;
     175        }
     176
     177        /**
     178         * The text shown when no items are found.
     179         *
     180         * Nice job, clean sheet!
     181         *
     182         * @since 8.0.0
     183         */
     184        public function no_items() {
     185                esc_html_e( 'No opt-outs found.', 'buddypress' );
     186        }
     187
     188        /**
     189         * The columns opt-outs can be reordered by.
     190         *
     191         * @since 8.0.0
     192         */
     193        public function get_sortable_columns() {
     194                return array(
     195                        'email_address'            => 'email_address_hash',
     196                        'username'                 => 'user_id',
     197                        'email_type'               => 'email_type',
     198                        'optout_date_modified'     => 'date_modified',
     199                );
     200        }
     201
     202        /**
     203         * Display opt-out rows.
     204         *
     205         * @since 8.0.0
     206         */
     207        public function display_rows() {
     208                $style = '';
     209                foreach ( $this->items as $optout ) {
     210                        $style = ( ' class="alternate"' == $style ) ? '' : ' class="alternate"';
     211                        echo "\n\t" . $this->single_row( $optout, $style );
     212                }
     213        }
     214
     215        /**
     216         * Display an opt-out row.
     217         *
     218         * @since 8.0.0
     219         *
     220         * @see WP_List_Table::single_row() for explanation of params.
     221         *
     222         * @param BP_Optout $optout   BP_Optout object.
     223         * @param string    $style    Styles for the row.
     224         * @param string    $role     Role to be assigned to user.
     225         * @param int       $numposts Number of posts.
     226         * @return void
     227         */
     228        public function single_row( $optout = null, $style = '', $role = '', $numposts = 0 ) {
     229                echo '<tr' . $style . ' id="optout-' . esc_attr( $optout->id ) . '">';
     230                echo $this->single_row_columns( $optout );
     231                echo '</tr>';
     232        }
     233
     234        /**
     235         * Markup for the checkbox used to select items for bulk actions.
     236         *
     237         * @since 8.0.0
     238         *
     239         * @param BP_Optout $optout BP_Optout object.
     240         */
     241        public function column_cb( $optout = null ) {
     242        ?>
     243                <label class="screen-reader-text" for="optout_<?php echo intval( $optout->id ); ?>"><?php
     244                        /* translators: accessibility text */
     245                        printf( esc_html__( 'Select opt-out request: %s', 'buddypress' ), $optout->id );
     246                ?></label>
     247                <input type="checkbox" id="optout_<?php echo intval( $optout->id ) ?>" name="optout_ids[]" value="<?php echo esc_attr( $optout->id ) ?>" />
     248                <?php
     249        }
     250
     251        /**
     252         * Markup for the checkbox used to select items for bulk actions.
     253         *
     254         * @since 8.0.0
     255         *
     256         * @param BP_Optout $optout BP_Optout object.
     257         */
     258        public function column_email_address( $optout = null ) {
     259                printf( '<a href="mailto:%1$s">%2$s</a>', esc_attr( $optout->email_address ), esc_html( $optout->email_address ) );
     260
     261                $actions = array();
     262
     263                if ( is_network_admin() ) {
     264                        $form_url = network_admin_url( 'network-tools' );
     265                } else {
     266                        $form_url = bp_get_admin_url( 'tools.php' );
     267                }
     268
     269                // Delete link.
     270                $delete_link = add_query_arg(
     271                        array(
     272                                'page'      => 'bp-optouts',
     273                                'optout_id' => $optout->id,
     274                                'action'    => 'delete',
     275                        ),
     276                        $form_url
     277                );
     278                $actions['delete'] = sprintf( '<a href="%1$s" class="delete">%2$s</a>', esc_url( $delete_link ), __( 'Delete', 'buddypress' ) );
     279
     280                /**
     281                 * Filters the row actions for each opt-out in list.
     282                 *
     283                 * @since 8.0.0
     284                 *
     285                 * @param array  $actions Array of actions and corresponding links.
     286                 * @param object $optout  The BP_Optout.
     287                 */
     288                $actions = apply_filters( 'bp_optouts_management_row_actions', $actions, $optout );
     289
     290                echo $this->row_actions( $actions );
     291        }
     292
     293        /**
     294         * The inviter/site member who sent the email that prompted the opt-out.
     295         *
     296         * @since 8.0.0
     297         *
     298         * @param BP_Optout $optout BP_Optout object.
     299         */
     300        public function column_username( $optout = null ) {
     301                $avatar = get_avatar( $optout->user_id, 32 );
     302                $inviter = get_user_by( 'id', $optout->user_id );
     303                if ( ! $inviter ) {
     304                        return;
     305                }
     306                $user_link = bp_core_get_user_domain( $optout->user_id );
     307                echo $avatar . sprintf( '<strong><a href="%1$s" class="edit">%2$s</a></strong><br/>', esc_url( $user_link ), $inviter->user_login );
     308        }
     309
     310        /**
     311         * Display registration date of user whose communication prompted opt-out.
     312         *
     313         * @since 8.0.0
     314         *
     315         * @param BP_Optout $optout BP_Optout object.
     316         */
     317        public function column_user_registered( $optout = null ) {
     318                $inviter = get_user_by( 'id', $optout->user_id );
     319                if ( ! $inviter ) {
     320                        return;
     321                }
     322                echo esc_html( mysql2date( 'Y/m/d g:i:s a', $inviter->user_registered  ) );
     323        }
     324
     325        /**
     326         * Display type of email that prompted opt-out.
     327         *
     328         * @since 8.0.0
     329         *
     330         * @param BP_Optout $optout BP_Optout object.
     331         */
     332        public function column_email_type( $optout = null ) {
     333                echo esc_html( $optout->email_type );
     334        }
     335
     336        /**
     337         * Display description of bp-email-type that prompted opt-out.
     338         *
     339         * @since 8.0.0
     340         *
     341         * @param BP_Optout $optout BP_Optout object.
     342         */
     343        public function column_email_type_description( $optout = null ) {
     344                $type_term = get_term_by( 'slug', $optout->email_type, 'bp-email-type' );
     345                if ( $type_term ) {
     346                        echo esc_html( $type_term->description );
     347                }
     348
     349        }
     350
     351        /**
     352         * Display opt-out date.
     353         *
     354         * @since 8.0.0
     355         *
     356         * @param BP_Optout $optout BP_Optout object.
     357         */
     358        public function column_optout_date_modified( $optout = null ) {
     359                echo esc_html( mysql2date( 'Y/m/d g:i:s a', $optout->date_modified ) );
     360        }
     361
     362        /**
     363         * Allow plugins to add custom columns.
     364         *
     365         * @since 8.0.0
     366         *
     367         * @param BP_Optout $optout      BP_Optout object.
     368         * @param string    $column_name The column name.
     369         * @return string
     370         */
     371        function column_default( $optout = null, $column_name = '' ) {
     372
     373                /**
     374                 * Filters the single site custom columns for plugins.
     375                 *
     376                 * @since 8.0.0
     377                 *
     378                 * @param string    $column_name The column name.
     379                 * @param BP_Optout $optout      BP_Optout object.
     380                 */
     381                return apply_filters( 'bp_optouts_management_custom_column', '', $column_name, $optout );
     382        }
     383}
  • 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 77777d645..92ee286a1 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}