Skip to:
Content

BuddyPress.org

Ticket #8448: 8448.2.diff

File 8448.2.diff, 58.0 KB (added by dcavins, 4 months ago)

Adds new db table, wp-admin list table and a few convenience functions to manage BP_Optouts. Stores hashed email addresses.

  • 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/**
  • 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}
  • 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..5e90b094c 100644
    class BP_Members_Admin { 
    140140                $this->users_url    = bp_get_admin_url( 'users.php' );
    141141                $this->users_screen = bp_core_do_network_admin() ? 'users-network' : 'users';
    142142
     143                $this->members_optouts_page = '';
     144
    143145                // Specific config: BuddyPress is not network activated.
    144146                $this->subsite_activated = (bool) is_multisite() && ! bp_is_network_activated();
    145147
    class BP_Members_Admin { 
    488490                                'bp-signups',
    489491                                array( $this, 'signups_admin' )
    490492                        );
     493
     494                        // Manage opt-outs.
     495                        $hooks['members_optouts'] = $this->members_optouts_page = add_users_page(
     496                                __( 'Manage Opt-outs',  'buddypress' ),
     497                                __( 'Manage Opt-outs',  'buddypress' ),
     498                                $this->capability,
     499                                'bp-members-optouts',
     500                                array( $this, 'optouts_admin' )
     501                        );
    491502                }
    492503
    493504                $edit_page         = 'user-edit';
    class BP_Members_Admin { 
    509520                        $this->user_page    .= '-network';
    510521                        $this->users_page   .= '-network';
    511522                        $this->signups_page .= '-network';
     523
     524                        $this->members_optouts_page .= '-network';
    512525                }
    513526
    514527                // Setup the screen ID's.
    class BP_Members_Admin { 
    25642577
    25652578                return $value;
    25662579        }
     2580
     2581        /**
     2582         * Set up the Opt-outs admin page.
     2583         *
     2584         * Loaded before the page is rendered, this function does all initial
     2585         * setup, including: processing form requests, registering contextual
     2586         * help, and setting up screen options.
     2587         *
     2588         * @since 8.0.0
     2589         *
     2590         * @global $bp_members_optouts_list_table
     2591         */
     2592        public function members_optouts_admin_load() {
     2593                global $bp_members_optouts_list_table;
     2594
     2595                // Build redirection URL.
     2596                $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'] );
     2597                $doaction    = bp_admin_list_table_current_bulk_action();
     2598
     2599                /**
     2600                 * Fires at the start of the nonmember optouts admin load.
     2601                 *
     2602                 * @since 8.0.0
     2603                 *
     2604                 * @param string $doaction Current bulk action being processed.
     2605                 * @param array  $_REQUEST Current $_REQUEST global.
     2606                 */
     2607                do_action( 'bp_members_optouts_admin_load', $doaction, $_REQUEST );
     2608
     2609                /**
     2610                 * Filters the allowed actions for use in the nonmember optouts admin page.
     2611                 *
     2612                 * @since 8.0.0
     2613                 *
     2614                 * @param array $value Array of allowed actions to use.
     2615                 */
     2616                $allowed_actions = apply_filters( 'bp_members_optouts_admin_allowed_actions', array( 'do_delete',  'do_resend' ) );
     2617
     2618                if ( ! in_array( $doaction, $allowed_actions ) || ( -1 == $doaction ) ) {
     2619
     2620                        $bp_members_optouts_list_table = self::get_list_table_class( 'BP_Members_Optouts_List_Table', 'users' );
     2621
     2622                        // The per_page screen option.
     2623                        add_screen_option( 'per_page', array( 'label' => _x( 'Nonmember opt-outs', 'Nonmember opt-outs per page (screen options)', 'buddypress' ) ) );
     2624
     2625                        get_current_screen()->add_help_tab( array(
     2626                                'id'      => 'bp-members-optouts-overview',
     2627                                'title'   => __( 'Overview', 'buddypress' ),
     2628                                'content' =>
     2629                                '<p>' . __( 'This is the administration screen for nonmember opt-outs on your site.', 'buddypress' ) . '</p>' .
     2630                                '<p>' . __( 'From the screen options, you can customize the displayed columns and the pagination of this screen.', 'buddypress' ) . '</p>' .
     2631                                '<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>' .
     2632                                '<p>' . __( 'Using the search form, you can find specific opt-outs more easily. The Email Address field will be included in the search.', 'buddypress' ) . '</p>'
     2633                        ) );
     2634
     2635                        get_current_screen()->add_help_tab( array(
     2636                                'id'      => 'bp-members-optouts-actions',
     2637                                'title'   => __( 'Actions', 'buddypress' ),
     2638                                'content' =>
     2639                                '<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>' .
     2640                                '<ul><li>' . __( '"Delete" allows you to delete the record of an opt-out. You will be asked to confirm this deletion.', 'buddypress' ) . '</li></ul>' .
     2641                                '<p>' . __( 'Bulk actions allow you to perform these actions for the selected rows.', 'buddypress' ) . '</p>'
     2642                        ) );
     2643
     2644                        // Help panel - sidebar links.
     2645                        get_current_screen()->set_help_sidebar(
     2646                                '<p><strong>' . __( 'For more information:', 'buddypress' ) . '</strong></p>' .
     2647                                '<p>' . __( '<a href="https://buddypress.org/support/">Support Forums</a>', 'buddypress' ) . '</p>'
     2648                        );
     2649
     2650                        // Add accessible hidden headings and text for the Pending Users screen.
     2651                        get_current_screen()->set_screen_reader_content( array(
     2652                                /* translators: accessibility text */
     2653                                'heading_views'      => __( 'Filter opt-outs list', 'buddypress' ),
     2654                                /* translators: accessibility text */
     2655                                'heading_pagination' => __( 'Opt-out list navigation', 'buddypress' ),
     2656                                /* translators: accessibility text */
     2657                                'heading_list'       => __( 'Opt-outs list', 'buddypress' ),
     2658                        ) );
     2659
     2660                } else {
     2661                        if ( empty( $_REQUEST['optout_ids' ] ) ) {
     2662                                return;
     2663                        }
     2664                        $optout_ids = wp_parse_id_list( $_REQUEST['optout_ids' ] );
     2665
     2666                        // Handle optout deletion.
     2667                        if ( 'do_delete' == $doaction ) {
     2668
     2669                                // Nonce check.
     2670                                check_admin_referer( 'optouts_delete' );
     2671
     2672                                $success = 0;
     2673                                foreach ( $optout_ids as $optout_id ) {
     2674                                        if ( bp_delete_optout_by_id( $optout_id ) ) {
     2675                                                $success++;
     2676                                        }
     2677                                }
     2678
     2679                                $query_arg = array( 'updated' => 'deleted' );
     2680
     2681                                if ( ! empty( $success ) ) {
     2682                                        $query_arg['deleted'] = $success;
     2683                                }
     2684
     2685                                $notdeleted = count( $optout_ids ) - $success;
     2686                                if ( $notdeleted > 0 ) {
     2687                                        $query_arg['notdeleted'] = $notdeleted;
     2688                                }
     2689
     2690                                $redirect_to = add_query_arg( $query_arg, $redirect_to );
     2691
     2692                                bp_core_redirect( $redirect_to );
     2693
     2694                        // Plugins can update other stuff from here.
     2695                        } else {
     2696                                $this->redirect = $redirect_to;
     2697
     2698                                /**
     2699                                 * Fires at end of member opt-outs admin load
     2700                                 * if doaction does not match any actions.
     2701                                 *
     2702                                 * @since 2.0.0
     2703                                 *
     2704                                 * @param string $doaction Current bulk action being processed.
     2705                                 * @param array  $_REQUEST Current $_REQUEST global.
     2706                                 * @param string $redirect Determined redirect url to send user to.
     2707                                 */
     2708                                do_action( 'bp_members_admin_update_optouts', $doaction, $_REQUEST, $this->redirect );
     2709
     2710                                bp_core_redirect( $this->redirect );
     2711                        }
     2712                }
     2713        }
     2714
     2715        /**
     2716         * Get admin notice when viewing the optouts management page.
     2717         *
     2718         * @since 8.0.0
     2719         *
     2720         * @return array
     2721         */
     2722        private function get_members_optouts_notice() {
     2723
     2724                // Setup empty notice for return value.
     2725                $notice = array();
     2726
     2727                // Updates.
     2728                if ( ! empty( $_REQUEST['updated'] ) && 'deleted' === $_REQUEST['updated'] ) {
     2729                        $notice = array(
     2730                                'class'   => 'updated',
     2731                                'message' => ''
     2732                        );
     2733
     2734                        if ( ! empty( $_REQUEST['deleted'] ) ) {
     2735                                $notice['message'] .= sprintf(
     2736                                        /* translators: %s: number of deleted optouts */
     2737                                        _nx( '%s optout successfully deleted!', '%s optouts successfully deleted!',
     2738                                         absint( $_REQUEST['deleted'] ),
     2739                                         'members optout deleted',
     2740                                         'buddypress'
     2741                                        ),
     2742                                        number_format_i18n( absint( $_REQUEST['deleted'] ) )
     2743                                );
     2744                        }
     2745
     2746                        if ( ! empty( $_REQUEST['notdeleted'] ) ) {
     2747                                $notice['message'] .= sprintf(
     2748                                        /* translators: %s: number of optouts that failed to be deleted */
     2749                                        _nx( '%s optout was not deleted.', '%s optouts were not deleted.',
     2750                                         absint( $_REQUEST['notdeleted'] ),
     2751                                         'members optout notdeleted',
     2752                                         'buddypress'
     2753                                        ),
     2754                                        number_format_i18n( absint( $_REQUEST['notdeleted'] ) )
     2755                                );
     2756
     2757                                if ( empty( $_REQUEST['deleted'] ) ) {
     2758                                        $notice['class'] = 'error';
     2759                                }
     2760                        }
     2761                }
     2762
     2763                // Errors.
     2764                if ( ! empty( $_REQUEST['error'] ) && 'do_delete' === $_REQUEST['error'] ) {
     2765                        $notice = array(
     2766                                'class'   => 'error',
     2767                                'message' => esc_html__( 'There was a problem deleting optouts. Please try again.', 'buddypress' ),
     2768                        );
     2769                }
     2770
     2771                return $notice;
     2772        }
     2773
     2774        /**
     2775         * Member opt-outs admin page router.
     2776         *
     2777         * Depending on the context, display
     2778         * - the list of optouts,
     2779         * - or the delete confirmation screen,
     2780         *
     2781         * Also prepare the admin notices.
     2782         *
     2783         * @since 8.0.0
     2784         */
     2785        public function optouts_admin() {
     2786                $doaction = bp_admin_list_table_current_bulk_action();
     2787
     2788                // Prepare notices for admin.
     2789                $notice = $this->get_members_optouts_notice();
     2790
     2791                // Display notices.
     2792                if ( ! empty( $notice ) ) :
     2793                        if ( 'updated' === $notice['class'] ) : ?>
     2794
     2795                                <div id="message" class="<?php echo esc_attr( $notice['class'] ); ?> notice is-dismissible">
     2796
     2797                        <?php else: ?>
     2798
     2799                                <div class="<?php echo esc_attr( $notice['class'] ); ?> notice is-dismissible">
     2800
     2801                        <?php endif; ?>
     2802
     2803                                <p><?php echo $notice['message']; ?></p>
     2804                        </div>
     2805
     2806                <?php endif;
     2807
     2808                // Show the proper screen.
     2809                switch ( $doaction ) {
     2810                        case 'delete' :
     2811                                $this->optouts_admin_manage( $doaction );
     2812                                break;
     2813
     2814                        default:
     2815                                $this->optouts_admin_index();
     2816                                break;
     2817                }
     2818        }
     2819
     2820        /**
     2821         * This is the list of optouts.
     2822         *
     2823         * @since 8.0.0
     2824         *
     2825         * @global $plugin_page
     2826         * @global $bp_members_optouts_list_table
     2827         */
     2828        public function optouts_admin_index() {
     2829                global $plugin_page, $bp_members_optouts_list_table;
     2830
     2831                $usersearch = ! empty( $_REQUEST['s'] ) ? stripslashes( $_REQUEST['s'] ) : '';
     2832
     2833                // Prepare the group items for display.
     2834                $bp_members_optouts_list_table->prepare_items();
     2835
     2836                if ( is_network_admin() ) {
     2837                        $form_url = network_admin_url( 'users.php' );
     2838                } else {
     2839                        $form_url = bp_get_admin_url( 'users.php' );
     2840                }
     2841
     2842                $form_url = add_query_arg(
     2843                        array(
     2844                                'page' => 'bp-members-optouts',
     2845                        ),
     2846                        $form_url
     2847                );
     2848
     2849                $search_form_url = remove_query_arg(
     2850                        array(
     2851                                'action',
     2852                                'deleted',
     2853                                'notdeleted',
     2854                                'error',
     2855                                'updated',
     2856                                'delete',
     2857                                'activate',
     2858                                'activated',
     2859                                'notactivated',
     2860                                'resend',
     2861                                'resent',
     2862                                'notresent',
     2863                                'do_delete',
     2864                                'do_activate',
     2865                                'do_resend',
     2866                                'action2',
     2867                                '_wpnonce',
     2868                                'optout_ids'
     2869                        ), $_SERVER['REQUEST_URI']
     2870                );
     2871
     2872                ?>
     2873
     2874                <div class="wrap">
     2875                        <h1 class="wp-heading-inline"><?php _e( 'Nonmember Opt-outs', 'buddypress' ); ?></h1>
     2876
     2877                        <?php
     2878                        if ( $usersearch ) {
     2879                                printf( '<span class="subtitle">' . __( 'Search results for &#8220;%s&#8221;', 'buddypress' ) . '</span>', esc_html( $usersearch ) );
     2880                        }
     2881                        ?>
     2882                        <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>
     2883
     2884                        <hr class="wp-header-end">
     2885
     2886                        <?php // Display each opt-out on its own row. ?>
     2887                        <?php $bp_members_optouts_list_table->views(); ?>
     2888
     2889                        <form id="bp-members-optouts-search-form" action="<?php echo esc_url( $search_form_url ) ;?>">
     2890                                <input type="hidden" name="page" value="<?php echo esc_attr( $plugin_page ); ?>" />
     2891                                <?php $bp_members_optouts_list_table->search_box( __( 'Search opt-outs', 'buddypress' ), 'bp-members-optouts' ); ?>
     2892                        </form>
     2893
     2894                        <form id="bp-members-optouts-form" action="<?php echo esc_url( $form_url );?>" method="post">
     2895                                <?php $bp_members_optouts_list_table->display(); ?>
     2896                        </form>
     2897                </div>
     2898        <?php
     2899        }
     2900
     2901        /**
     2902         * This is the confirmation screen for actions.
     2903         *
     2904         * @since 2.0.0
     2905         *
     2906         * @param string $action Delete or resend optout.
     2907         *
     2908         * @return null|false
     2909         */
     2910        public function optouts_admin_manage( $action = '' ) {
     2911                if ( ! current_user_can( $this->capability ) || empty( $action ) ) {
     2912                        die( '-1' );
     2913                }
     2914
     2915                // Get the IDs from the URL.
     2916                $ids = false;
     2917                if ( ! empty( $_POST['optout_ids'] ) ) {
     2918                        $ids = wp_parse_id_list( $_POST['optout_ids'] );
     2919                } elseif ( ! empty( $_GET['optout_id'] ) ) {
     2920                        $ids = absint( $_GET['optout_id'] );
     2921                }
     2922
     2923                if ( empty( $ids ) ) {
     2924                        return false;
     2925                }
     2926
     2927                // Query for matching optouts, and filter out bad IDs.
     2928                $args = array(
     2929                        'id'     => $ids,
     2930                );
     2931                $optouts    = bp_get_optouts( $args );
     2932                $optout_ids = wp_list_pluck( $optouts, 'id' );
     2933
     2934                // Check optout IDs and set up strings.
     2935                switch ( $action ) {
     2936                        case 'delete' :
     2937                                $header_text = __( 'Delete optouts', 'buddypress' );
     2938                                if ( 1 == count( $optouts ) ) {
     2939                                        $helper_text = __( 'You are about to delete the following opt-out request:', 'buddypress' );
     2940                                } else {
     2941                                        $helper_text = __( 'You are about to delete the following opt-out requests:', 'buddypress' );
     2942                                }
     2943                                break;
     2944                }
     2945
     2946                // These arguments are added to all URLs.
     2947                $url_args = array( 'page' => 'bp-members-optouts' );
     2948
     2949                // These arguments are only added when performing an action.
     2950                $action_args = array(
     2951                        'action'     => 'do_' . $action,
     2952                        'optout_ids' => implode( ',', $optout_ids )
     2953                );
     2954
     2955                if ( is_network_admin() ) {
     2956                        $base_url = network_admin_url( 'users.php' );
     2957                } else {
     2958                        $base_url = bp_get_admin_url( 'users.php' );
     2959                }
     2960
     2961                $cancel_url = add_query_arg( $url_args, $base_url );
     2962                $action_url = wp_nonce_url(
     2963                        add_query_arg(
     2964                                array_merge( $url_args, $action_args ),
     2965                                $base_url
     2966                        ),
     2967                        'optouts_' . $action
     2968                );
     2969
     2970                ?>
     2971
     2972                <div class="wrap">
     2973                        <h1 class="wp-heading-inline"><?php echo esc_html( $header_text ); ?></h1>
     2974                        <hr class="wp-header-end">
     2975
     2976                        <p><?php echo esc_html( $helper_text ); ?></p>
     2977
     2978                        <ol class="bp-optouts-list">
     2979                        <?php foreach ( $optouts as $optout ) : ?>
     2980
     2981                                <li>
     2982                                        <strong><?php echo esc_html( $optout->email_address ) ?></strong>
     2983                                        <p class="description">
     2984                                                <?php
     2985                                                $last_modified = mysql2date( 'Y/m/d g:i:s a', $optout->date_modified );
     2986                                                /* translators: %s: modification date */
     2987                                                printf( esc_html__( 'Date modified: %s', 'buddypress'), $last_modified );
     2988                                                ?>
     2989                                        </p>
     2990                                </li>
     2991
     2992                        <?php endforeach; ?>
     2993                        </ol>
     2994
     2995                        <?php if ( 'delete' === $action ) : ?>
     2996
     2997                                <p><strong><?php esc_html_e( 'This action cannot be undone.', 'buddypress' ) ?></strong></p>
     2998
     2999                        <?php endif ; ?>
     3000
     3001                        <a class="button-primary" href="<?php echo esc_url( $action_url ); ?>"><?php esc_html_e( 'Confirm', 'buddypress' ); ?></a>
     3002                        <a class="button" href="<?php echo esc_url( $cancel_url ); ?>"><?php esc_html_e( 'Cancel', 'buddypress' ) ?></a>
     3003                </div>
     3004
     3005                <?php
     3006        }
     3007
    25673008}
    25683009endif; // 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                );
  • new file src/bp-members/classes/class-bp-members-optouts-list-table.php

    diff --git src/bp-members/classes/class-bp-members-optouts-list-table.php src/bp-members/classes/class-bp-members-optouts-list-table.php
    new file mode 100644
    index 000000000..d50e40cc6
    - +  
     1<?php
     2/**
     3 * BuddyPress Members Opt-outs List Table class.
     4 *
     5 * @package BuddyPress
     6 * @subpackage MembersAdminClasses
     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_Members_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 (e.g. "all", "public").
     87         *
     88         * @since 8.0.0
     89         */
     90        public function views() {
     91                $url_base = add_query_arg(
     92                        array(
     93                                'page' => 'bp-members-optouts',
     94                        ),
     95                        bp_get_admin_url( 'users.php' )
     96                );
     97                ?>
     98
     99                <h2 class="screen-reader-text"><?php
     100                        /* translators: accessibility text */
     101                        _e( 'Filter optouts list', 'buddypress' );
     102                ?></h2>
     103                <ul class="subsubsub">
     104                        <?php
     105                        /**
     106                         * Fires inside listing of views so plugins can add their own.
     107                         *
     108                         * @since 8.0.0
     109                         *
     110                         * @param string $url_base       Current URL base for view.
     111                         * @param array  $active_filters Current filters being requested.
     112                         */
     113                        do_action( 'bp_members_optouts_list_table_get_views', $url_base, $this->active_filters ); ?>
     114                </ul>
     115        <?php
     116        }
     117
     118        /**
     119         * Get rid of the extra nav.
     120         *
     121         * WP_Users_List_Table will add an extra nav to change user's role.
     122         * As we're dealing with opt-outs, we don't need this.
     123         *
     124         * @since 8.0.0
     125         *
     126         * @param array $which Current table nav item.
     127         */
     128        public function extra_tablenav( $which ) {
     129                return;
     130        }
     131
     132        /**
     133         * Specific opt-out columns.
     134         *
     135         * @since 8.0.0
     136         *
     137         * @return array
     138         */
     139        public function get_columns() {
     140                /**
     141                 * Filters the nonmember opt-outs columns.
     142                 *
     143                 * @since 8.0.0
     144                 *
     145                 * @param array $value Array of columns to display.
     146                 */
     147                return apply_filters( 'bp_members_optouts_list_columns', array(
     148                        'cb'                       => '<input type="checkbox" />',
     149                        'email_address'            => __( 'Email Address Hash',    'buddypress' ),
     150                        'username'                 => __( 'Email Sender',        'buddypress' ),
     151                        'user_registered'          => __( 'Email Sender Registered',        'buddypress' ),
     152                        'email_type'               => __( 'Email Type', 'buddypress' ),
     153                        'email_type_description'   => __( 'Email Description', 'buddypress' ),
     154                        'optout_date_modified'     => __( 'Date Modified',   'buddypress' ),
     155                ) );
     156        }
     157
     158        /**
     159         * Specific bulk actions for opt-outs.
     160         *
     161         * @since 8.0.0
     162         */
     163        public function get_bulk_actions() {
     164                if ( current_user_can( 'delete_users' ) ) {
     165                        $actions['delete'] = _x( 'Delete', 'Optout database record action', 'buddypress' );
     166                }
     167
     168                return $actions;
     169        }
     170
     171        /**
     172         * The text shown when no items are found.
     173         *
     174         * Nice job, clean sheet!
     175         *
     176         * @since 8.0.0
     177         */
     178        public function no_items() {
     179                esc_html_e( 'No opt-outs found.', 'buddypress' );
     180        }
     181
     182        /**
     183         * The columns opt-outs can be reordered by.
     184         *
     185         * @since 8.0.0
     186         */
     187        public function get_sortable_columns() {
     188                return array(
     189                        'email_address'            => 'email_address_hash',
     190                        'username'                 => 'user_id',
     191                        'email_type'               => 'email_type',
     192                        'optout_date_modified'     => 'date_modified',
     193                );
     194        }
     195
     196        /**
     197         * Display opt-out rows.
     198         *
     199         * @since 8.0.0
     200         */
     201        public function display_rows() {
     202                $style = '';
     203                foreach ( $this->items as $optout ) {
     204                        $style = ( ' class="alternate"' == $style ) ? '' : ' class="alternate"';
     205                        echo "\n\t" . $this->single_row( $optout, $style );
     206                }
     207        }
     208
     209        /**
     210         * Display an opt-out row.
     211         *
     212         * @since 8.0.0
     213         *
     214         * @see WP_List_Table::single_row() for explanation of params.
     215         *
     216         * @param BP_Optout $optout   BP_Optout object.
     217         * @param string    $style    Styles for the row.
     218         * @param string    $role     Role to be assigned to user.
     219         * @param int       $numposts Number of posts.
     220         * @return void
     221         */
     222        public function single_row( $optout = null, $style = '', $role = '', $numposts = 0 ) {
     223                echo '<tr' . $style . ' id="optout-' . esc_attr( $optout->id ) . '">';
     224                echo $this->single_row_columns( $optout );
     225                echo '</tr>';
     226        }
     227
     228        /**
     229         * Markup for the checkbox used to select items for bulk actions.
     230         *
     231         * @since 8.0.0
     232         *
     233         * @param BP_Optout $optout BP_Optout object.
     234         */
     235        public function column_cb( $optout = null ) {
     236        ?>
     237                <label class="screen-reader-text" for="optout_<?php echo intval( $optout->id ); ?>"><?php
     238                        /* translators: accessibility text */
     239                        printf( esc_html__( 'Select opt-out request: %s', 'buddypress' ), $optout->id );
     240                ?></label>
     241                <input type="checkbox" id="optout_<?php echo intval( $optout->id ) ?>" name="optout_ids[]" value="<?php echo esc_attr( $optout->id ) ?>" />
     242                <?php
     243        }
     244
     245        /**
     246         * Markup for the checkbox used to select items for bulk actions.
     247         *
     248         * @since 8.0.0
     249         *
     250         * @param BP_Optout $optout BP_Optout object.
     251         */
     252        public function column_email_address( $optout = null ) {
     253                printf( '<a href="mailto:%1$s">%2$s</a>', esc_attr( $optout->email_address ), esc_html( $optout->email_address ) );
     254
     255                $actions = array();
     256
     257                // Delete link.
     258                $delete_link = add_query_arg(
     259                        array(
     260                                'page'      => 'bp-members-optouts',
     261                                'optout_id' => $optout->id,
     262                                'action'    => 'delete',
     263                        ),
     264                        bp_get_admin_url( 'users.php' )
     265                );
     266                $actions['delete'] = sprintf( '<a href="%1$s" class="delete">%2$s</a>', esc_url( $delete_link ), __( 'Delete', 'buddypress' ) );
     267
     268                /**
     269                 * Filters the row actions for each opt-out in list.
     270                 *
     271                 * @since 8.0.0
     272                 *
     273                 * @param array  $actions Array of actions and corresponding links.
     274                 * @param object $optout  The BP_Optout.
     275                 */
     276                $actions = apply_filters( 'bp_members_optouts_management_row_actions', $actions, $optout );
     277
     278                echo $this->row_actions( $actions );
     279        }
     280
     281        /**
     282         * The inviter/site member who sent the email that prompted the opt-out.
     283         *
     284         * @since 8.0.0
     285         *
     286         * @param BP_Optout $optout BP_Optout object.
     287         */
     288        public function column_username( $optout = null ) {
     289                $avatar = get_avatar( $optout->user_id, 32 );
     290                $inviter = get_user_by( 'id', $optout->user_id );
     291                if ( ! $inviter ) {
     292                        return;
     293                }
     294                $user_link = bp_core_get_user_domain( $optout->user_id );
     295                echo $avatar . sprintf( '<strong><a href="%1$s" class="edit">%2$s</a></strong><br/>', esc_url( $user_link ), $inviter->user_login );
     296        }
     297
     298        /**
     299         * Display registration date of user whose communication prompted opt-out.
     300         *
     301         * @since 8.0.0
     302         *
     303         * @param BP_Optout $optout BP_Optout object.
     304         */
     305        public function column_user_registered( $optout = null ) {
     306                $inviter = get_user_by( 'id', $optout->user_id );
     307                if ( ! $inviter ) {
     308                        return;
     309                }
     310                echo esc_html( mysql2date( 'Y/m/d g:i:s a', $inviter->user_registered  ) );
     311        }
     312
     313        /**
     314         * Display type of email that prompted opt-out.
     315         *
     316         * @since 8.0.0
     317         *
     318         * @param BP_Optout $optout BP_Optout object.
     319         */
     320        public function column_email_type( $optout = null ) {
     321                echo esc_html( $optout->email_type );
     322        }
     323
     324        /**
     325         * Display description of bp-email-type that prompted opt-out.
     326         *
     327         * @since 8.0.0
     328         *
     329         * @param BP_Optout $optout BP_Optout object.
     330         */
     331        public function column_email_type_description( $optout = null ) {
     332                $type_term = get_term_by( 'slug', $optout->email_type, 'bp-email-type' );
     333                if ( $type_term ) {
     334                        echo esc_html( $type_term->description );
     335                }
     336
     337        }
     338
     339        /**
     340         * Display opt-out date.
     341         *
     342         * @since 8.0.0
     343         *
     344         * @param BP_Optout $optout BP_Optout object.
     345         */
     346        public function column_optout_date_modified( $optout = null ) {
     347                echo esc_html( mysql2date( 'Y/m/d g:i:s a', $optout->date_modified ) );
     348        }
     349
     350        /**
     351         * Allow plugins to add custom columns.
     352         *
     353         * @since 8.0.0
     354         *
     355         * @param BP_Optout $optout      BP_Optout object.
     356         * @param string    $column_name The column name.
     357         * @return string
     358         */
     359        function column_default( $optout = null, $column_name = '' ) {
     360
     361                /**
     362                 * Filters the single site custom columns for plugins.
     363                 *
     364                 * @since 8.0.0
     365                 *
     366                 * @param string    $column_name The column name.
     367                 * @param BP_Optout $optout      BP_Optout object.
     368                 */
     369                return apply_filters( 'bp_members_optouts_management_custom_column', '', $column_name, $optout );
     370        }
     371}
  • src/class-buddypress.php

    diff --git src/class-buddypress.php src/class-buddypress.php
    index 3dd1d483c..a571fe3b3 100644
    class BuddyPress { 
    597597                        'BP_REST_Components_Endpoint'  => 'core',
    598598                        'BP_REST_Attachments'          => 'core',
    599599                        'BP_Admin_Types'               => 'core',
     600                        'BP_Optout'                    => 'core',
    600601
    601602                        'BP_Core_Friends_Widget'   => 'friends',
    602603                        '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}