Ticket #8448: 8448.3.patch
File 8448.3.patch, 59.1 KB (added by , 2 years ago) |
---|
-
src/bp-core/admin/bp-core-admin-functions.php
diff --git src/bp-core/admin/bp-core-admin-functions.php src/bp-core/admin/bp-core-admin-functions.php index c9a5259e9..26f549d8f 100644
function bp_core_modify_admin_menu_highlight() { 81 81 } 82 82 83 83 // Network Admin > Tools. 84 if ( in_array( $plugin_page, array( 'bp-tools', 'available-tools' ) ) ) {84 if ( in_array( $plugin_page, array( 'bp-tools', 'available-tools', 'bp-optouts' ) ) ) { 85 85 $submenu_file = $plugin_page; 86 86 } 87 87 } -
new file src/bp-core/admin/bp-core-admin-optouts.php
diff --git src/bp-core/admin/bp-core-admin-optouts.php src/bp-core/admin/bp-core-admin-optouts.php new file mode 100644 index 000000000..a80b188e4
- + 1 <?php 2 /** 3 * BuddyPress Opt-outs management. 4 * 5 * @package BuddyPress 6 * @subpackage Core 7 * @since 8.0.0 8 */ 9 10 // Exit if accessed directly. 11 defined( 'ABSPATH' ) || exit; 12 13 /** 14 * Set up the Opt-outs admin page. 15 * 16 * Loaded before the page is rendered, this function does all initial 17 * setup, including: processing form requests, registering contextual 18 * help, and setting up screen options. 19 * 20 * @since 8.0.0 21 * 22 * @global $bp_optouts_list_table 23 */ 24 function bp_core_optouts_admin_load() { 25 global $bp_optouts_list_table; 26 27 // Build redirection URL. 28 $redirect_to = remove_query_arg( array( 'action', 'error', 'updated', 'activated', 'notactivated', 'deleted', 'notdeleted', 'resent', 'notresent', 'do_delete', 'do_resend', 'do_activate', '_wpnonce', 'signup_ids' ), $_SERVER['REQUEST_URI'] ); 29 $doaction = bp_admin_list_table_current_bulk_action(); 30 31 /** 32 * Fires at the start of the nonmember opt-outs admin load. 33 * 34 * @since 8.0.0 35 * 36 * @param string $doaction Current bulk action being processed. 37 * @param array $_REQUEST Current $_REQUEST global. 38 */ 39 do_action( 'bp_optouts_admin_load', $doaction, $_REQUEST ); 40 41 /** 42 * Filters the allowed actions for use in the nonmember optouts admin page. 43 * 44 * @since 8.0.0 45 * 46 * @param array $value Array of allowed actions to use. 47 */ 48 $allowed_actions = apply_filters( 'bp_optouts_admin_allowed_actions', array( 'do_delete', 'do_resend' ) ); 49 50 if ( ! in_array( $doaction, $allowed_actions ) || ( -1 == $doaction ) ) { 51 52 require_once( ABSPATH . 'wp-admin/includes/class-wp-users-list-table.php' ); 53 $bp_optouts_list_table = new BP_Optouts_List_Table(); 54 55 // The per_page screen option. 56 add_screen_option( 'per_page', array( 'label' => _x( 'Nonmember opt-outs', 'Nonmember opt-outs per page (screen options)', 'buddypress' ) ) ); 57 58 get_current_screen()->add_help_tab( array( 59 'id' => 'bp-optouts-overview', 60 'title' => __( 'Overview', 'buddypress' ), 61 'content' => 62 '<p>' . __( 'This is the administration screen for nonmember opt-outs on your site.', 'buddypress' ) . '</p>' . 63 '<p>' . __( 'From the screen options, you can customize the displayed columns and the pagination of this screen.', 'buddypress' ) . '</p>' . 64 '<p>' . __( 'You can reorder the list of opt-outs by clicking on the Email Address, User Who Contacted, Email Type or Date Modified column headers.', 'buddypress' ) . '</p>' . 65 '<p>' . __( 'Using the search form, you can search for an opt-out to a specific email address.', 'buddypress' ) . '</p>' 66 ) ); 67 68 get_current_screen()->add_help_tab( array( 69 'id' => 'bp-optouts-actions', 70 'title' => __( 'Actions', 'buddypress' ), 71 'content' => 72 '<p>' . __( 'Hovering over a row in the opt-outs list will display action links that allow you to manage the opt-out. You can perform the following actions:', 'buddypress' ) . '</p>' . 73 '<ul><li>' . __( '"Delete" allows you to delete the record of an opt-out. You will be asked to confirm this deletion.', 'buddypress' ) . '</li></ul>' . 74 '<p>' . __( 'Bulk actions allow you to perform these actions for the selected rows.', 'buddypress' ) . '</p>' 75 ) ); 76 77 // Help panel - sidebar links. 78 get_current_screen()->set_help_sidebar( 79 '<p><strong>' . __( 'For more information:', 'buddypress' ) . '</strong></p>' . 80 '<p>' . __( '<a href="https://buddypress.org/support/">Support Forums</a>', 'buddypress' ) . '</p>' 81 ); 82 83 // Add accessible hidden headings and text for the Pending Users screen. 84 get_current_screen()->set_screen_reader_content( array( 85 /* translators: accessibility text */ 86 'heading_views' => __( 'Filter opt-outs list', 'buddypress' ), 87 /* translators: accessibility text */ 88 'heading_pagination' => __( 'Opt-out list navigation', 'buddypress' ), 89 /* translators: accessibility text */ 90 'heading_list' => __( 'Opt-outs list', 'buddypress' ), 91 ) ); 92 93 } else { 94 if ( empty( $_REQUEST['optout_ids' ] ) ) { 95 return; 96 } 97 $optout_ids = wp_parse_id_list( $_REQUEST['optout_ids' ] ); 98 99 // Handle optout deletion. 100 if ( 'do_delete' == $doaction ) { 101 102 // Nonce check. 103 check_admin_referer( 'optouts_delete' ); 104 105 $success = 0; 106 foreach ( $optout_ids as $optout_id ) { 107 if ( bp_delete_optout_by_id( $optout_id ) ) { 108 $success++; 109 } 110 } 111 112 $query_arg = array( 'updated' => 'deleted' ); 113 114 if ( ! empty( $success ) ) { 115 $query_arg['deleted'] = $success; 116 } 117 118 $notdeleted = count( $optout_ids ) - $success; 119 if ( $notdeleted > 0 ) { 120 $query_arg['notdeleted'] = $notdeleted; 121 } 122 123 $redirect_to = add_query_arg( $query_arg, $redirect_to ); 124 125 bp_core_redirect( $redirect_to ); 126 127 // Plugins can update other stuff from here. 128 } else { 129 130 /** 131 * Fires at end of opt-outs admin load 132 * if doaction does not match any actions. 133 * 134 * @since 2.0.0 135 * 136 * @param string $doaction Current bulk action being processed. 137 * @param array $_REQUEST Current $_REQUEST global. 138 * @param string $redirect Determined redirect url to send user to. 139 */ 140 do_action( 'bp_core_admin_update_optouts', $doaction, $_REQUEST, $redirect_to ); 141 142 bp_core_redirect( $redirect_to ); 143 } 144 } 145 } 146 add_action( "load-tools_page_bp-optouts", 'bp_core_optouts_admin_load' ); 147 148 /** 149 * Get admin notice when viewing the optouts management page. 150 * 151 * @since 8.0.0 152 * 153 * @return array 154 */ 155 function bp_core_get_optouts_notice() { 156 157 // Setup empty notice for return value. 158 $notice = array(); 159 160 // Updates. 161 if ( ! empty( $_REQUEST['updated'] ) && 'deleted' === $_REQUEST['updated'] ) { 162 $notice = array( 163 'class' => 'updated', 164 'message' => '' 165 ); 166 167 if ( ! empty( $_REQUEST['deleted'] ) ) { 168 $notice['message'] .= sprintf( 169 /* translators: %s: number of deleted optouts */ 170 _nx( '%s optout successfully deleted!', '%s optouts successfully deleted!', 171 absint( $_REQUEST['deleted'] ), 172 'nonmembers optout deleted', 173 'buddypress' 174 ), 175 number_format_i18n( absint( $_REQUEST['deleted'] ) ) 176 ); 177 } 178 179 if ( ! empty( $_REQUEST['notdeleted'] ) ) { 180 $notice['message'] .= sprintf( 181 /* translators: %s: number of optouts that failed to be deleted */ 182 _nx( '%s optout was not deleted.', '%s optouts were not deleted.', 183 absint( $_REQUEST['notdeleted'] ), 184 'nonmembers optout not deleted', 185 'buddypress' 186 ), 187 number_format_i18n( absint( $_REQUEST['notdeleted'] ) ) 188 ); 189 190 if ( empty( $_REQUEST['deleted'] ) ) { 191 $notice['class'] = 'error'; 192 } 193 } 194 } 195 196 // Errors. 197 if ( ! empty( $_REQUEST['error'] ) && 'do_delete' === $_REQUEST['error'] ) { 198 $notice = array( 199 'class' => 'error', 200 'message' => esc_html__( 'There was a problem deleting optouts. Please try again.', 'buddypress' ), 201 ); 202 } 203 204 return $notice; 205 } 206 207 /** 208 * Opt-outs admin page router. 209 * 210 * Depending on the context, display 211 * - the list of optouts, 212 * - or the delete confirmation screen, 213 * 214 * Also prepare the admin notices. 215 * 216 * @since 8.0.0 217 */ 218 function bp_core_optouts_admin() { 219 $doaction = bp_admin_list_table_current_bulk_action(); 220 221 // Prepare notices for admin. 222 $notice = bp_core_get_optouts_notice(); 223 224 // Display notices. 225 if ( ! empty( $notice ) ) : 226 if ( 'updated' === $notice['class'] ) : ?> 227 228 <div id="message" class="<?php echo esc_attr( $notice['class'] ); ?> notice is-dismissible"> 229 230 <?php else: ?> 231 232 <div class="<?php echo esc_attr( $notice['class'] ); ?> notice is-dismissible"> 233 234 <?php endif; ?> 235 236 <p><?php echo $notice['message']; ?></p> 237 </div> 238 239 <?php endif; 240 241 // Show the proper screen. 242 switch ( $doaction ) { 243 case 'delete' : 244 bp_core_optouts_admin_manage( $doaction ); 245 break; 246 247 default: 248 bp_core_optouts_admin_index(); 249 break; 250 } 251 } 252 253 /** 254 * This is the list of optouts. 255 * 256 * @since 8.0.0 257 * 258 * @global $plugin_page 259 * @global $bp_optouts_list_table 260 */ 261 function bp_core_optouts_admin_index() { 262 global $plugin_page, $bp_optouts_list_table; 263 264 $usersearch = ! empty( $_REQUEST['s'] ) ? stripslashes( $_REQUEST['s'] ) : ''; 265 266 // Prepare the group items for display. 267 $bp_optouts_list_table->prepare_items(); 268 269 if ( is_network_admin() ) { 270 $form_url = network_admin_url( 'network-tools' ); 271 } else { 272 $form_url = bp_get_admin_url( 'tools.php' ); 273 } 274 275 $form_url = add_query_arg( 276 array( 277 'page' => 'bp-optouts', 278 ), 279 $form_url 280 ); 281 282 $search_form_url = remove_query_arg( 283 array( 284 'action', 285 'deleted', 286 'notdeleted', 287 'error', 288 'updated', 289 'delete', 290 'activate', 291 'activated', 292 'notactivated', 293 'resend', 294 'resent', 295 'notresent', 296 'do_delete', 297 'do_activate', 298 'do_resend', 299 'action2', 300 '_wpnonce', 301 'optout_ids' 302 ), $_SERVER['REQUEST_URI'] 303 ); 304 305 ?> 306 307 <div class="wrap"> 308 <h1 class="wp-heading-inline"><?php _e( 'Nonmember Opt-outs', 'buddypress' ); ?></h1> 309 310 <?php 311 if ( $usersearch ) { 312 printf( '<span class="subtitle">' . __( 'Opt-outs with an email address matching “%s”', 'buddypress' ) . '</span>', esc_html( $usersearch ) ); 313 } 314 ?> 315 <p class="description"><?php _e( 'This table shows opt-out requests from people who are not members of this site, but have been contacted via communication from this site, and wish to receive no further communications.', 'buddypress' ); ?></p> 316 317 <hr class="wp-header-end"> 318 319 <?php // Display each opt-out on its own row. ?> 320 <?php $bp_optouts_list_table->views(); ?> 321 322 <form id="bp-optouts-search-form" action="<?php echo esc_url( $search_form_url ) ;?>"> 323 <input type="hidden" name="page" value="<?php echo esc_attr( $plugin_page ); ?>" /> 324 <?php $bp_optouts_list_table->search_box( __( 'Search for a specific email address', 'buddypress' ), 'bp-optouts' ); ?> 325 </form> 326 327 <form id="bp-optouts-form" action="<?php echo esc_url( $form_url );?>" method="post"> 328 <?php $bp_optouts_list_table->display(); ?> 329 </form> 330 </div> 331 <?php 332 } 333 334 /** 335 * This is the confirmation screen for actions. 336 * 337 * @since 8.0.0 338 * 339 * @param string $action Delete or resend optout. 340 * 341 * @return null|false 342 */ 343 function bp_core_optouts_admin_manage( $action = '' ) { 344 $capability = bp_core_do_network_admin() ? 'manage_network_options' : 'manage_options'; 345 if ( ! current_user_can( $capability ) || empty( $action ) ) { 346 die( '-1' ); 347 } 348 349 // Get the IDs from the URL. 350 $ids = false; 351 if ( ! empty( $_POST['optout_ids'] ) ) { 352 $ids = wp_parse_id_list( $_POST['optout_ids'] ); 353 } elseif ( ! empty( $_GET['optout_id'] ) ) { 354 $ids = absint( $_GET['optout_id'] ); 355 } 356 357 if ( empty( $ids ) ) { 358 return false; 359 } 360 361 // Query for matching optouts, and filter out bad IDs. 362 $args = array( 363 'id' => $ids, 364 ); 365 $optouts = bp_get_optouts( $args ); 366 $optout_ids = wp_list_pluck( $optouts, 'id' ); 367 368 // Check optout IDs and set up strings. 369 switch ( $action ) { 370 case 'delete' : 371 $header_text = __( 'Delete optouts', 'buddypress' ); 372 if ( 1 == count( $optouts ) ) { 373 $helper_text = __( 'You are about to delete the following opt-out request:', 'buddypress' ); 374 } else { 375 $helper_text = __( 'You are about to delete the following opt-out requests:', 'buddypress' ); 376 } 377 break; 378 } 379 380 // These arguments are added to all URLs. 381 $url_args = array( 'page' => 'bp-optouts' ); 382 383 // These arguments are only added when performing an action. 384 $action_args = array( 385 'action' => 'do_' . $action, 386 'optout_ids' => implode( ',', $optout_ids ) 387 ); 388 389 if ( is_network_admin() ) { 390 $base_url = network_admin_url( 'network-tools' ); 391 } else { 392 $base_url = bp_get_admin_url( 'tools.php' ); 393 } 394 395 $cancel_url = add_query_arg( $url_args, $base_url ); 396 $action_url = wp_nonce_url( 397 add_query_arg( 398 array_merge( $url_args, $action_args ), 399 $base_url 400 ), 401 'optouts_' . $action 402 ); 403 404 ?> 405 406 <div class="wrap"> 407 <h1 class="wp-heading-inline"><?php echo esc_html( $header_text ); ?></h1> 408 <hr class="wp-header-end"> 409 410 <p><?php echo esc_html( $helper_text ); ?></p> 411 412 <ol class="bp-optouts-list"> 413 <?php foreach ( $optouts as $optout ) : ?> 414 415 <li> 416 <strong><?php echo esc_html( $optout->email_address ) ?></strong> 417 <p class="description"> 418 <?php 419 $last_modified = mysql2date( 'Y/m/d g:i:s a', $optout->date_modified ); 420 /* translators: %s: modification date */ 421 printf( esc_html__( 'Date modified: %s', 'buddypress'), $last_modified ); 422 ?> 423 </p> 424 </li> 425 426 <?php endforeach; ?> 427 </ol> 428 429 <?php if ( 'delete' === $action ) : ?> 430 431 <p><strong><?php esc_html_e( 'This action cannot be undone.', 'buddypress' ) ?></strong></p> 432 433 <?php endif ; ?> 434 435 <a class="button-primary" href="<?php echo esc_url( $action_url ); ?>"><?php esc_html_e( 'Confirm', 'buddypress' ); ?></a> 436 <a class="button" href="<?php echo esc_url( $cancel_url ); ?>"><?php esc_html_e( 'Cancel', 'buddypress' ) ?></a> 437 </div> 438 439 <?php 440 } -
src/bp-core/admin/bp-core-admin-schema.php
diff --git src/bp-core/admin/bp-core-admin-schema.php src/bp-core/admin/bp-core-admin-schema.php index d7cab5bbe..ac49a63ae 100644
function bp_core_install( $active_components = false ) { 40 40 // Install the invitations table. 41 41 bp_core_install_invitations(); 42 42 43 // Install the nonmember opt-outs table. 44 bp_core_install_nonmember_opt_outs(); 45 43 46 // Notifications. 44 47 if ( !empty( $active_components['notifications'] ) ) { 45 48 bp_core_install_notifications(); … … function bp_core_install_invitations() { 589 592 */ 590 593 do_action( 'bp_core_install_invitations' ); 591 594 } 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 */ 605 function 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 = '' ) { 415 415 add_action( 'bp_type_inserted', 'bp_clear_object_type_terms_cache' ); 416 416 add_action( 'bp_type_updated', 'bp_clear_object_type_terms_cache' ); 417 417 add_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 */ 424 function bp_optouts_reset_cache_incrementor() { 425 bp_core_reset_incrementor( 'bp_optouts' ); 426 } 427 add_action( 'bp_optout_after_save', 'bp_optouts_reset_cache_incrementor' ); 428 add_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 = '' ) { 4232 4232 */ 4233 4233 return apply_filters( 'bp_get_widget_max_count_limit', 50, $widget_class ); 4234 4234 } 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 */ 4252 function 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 */ 4293 function 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 */ 4305 function 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() { 621 621 '%s', 622 622 ) 623 623 ); 624 625 bp_core_install_nonmember_opt_outs(); 624 626 } 625 627 626 628 /** -
src/bp-core/classes/class-bp-admin.php
diff --git src/bp-core/classes/class-bp-admin.php src/bp-core/classes/class-bp-admin.php index 32dd0768d..2edd975c5 100644
class BP_Admin { 123 123 require( $this->admin_dir . 'bp-core-admin-components.php' ); 124 124 require( $this->admin_dir . 'bp-core-admin-slugs.php' ); 125 125 require( $this->admin_dir . 'bp-core-admin-tools.php' ); 126 require( $this->admin_dir . 'bp-core-admin-optouts.php' ); 126 127 } 127 128 128 129 /** … … class BP_Admin { 301 302 'bp_core_admin_tools' 302 303 ); 303 304 305 $hooks[] = add_submenu_page( 306 $tools_parent, 307 __( 'Manage Opt-outs', 'buddypress' ), 308 __( 'Manage Opt-outs', 'buddypress' ), 309 $this->capability, 310 'bp-optouts', 311 'bp_core_optouts_admin' 312 ); 313 304 314 // For network-wide configs, add a link to (the root site's) Emails screen. 305 315 if ( is_network_admin() && bp_is_network_activated() ) { 306 316 $email_labels = bp_get_email_post_type_labels(); -
new file src/bp-core/classes/class-bp-optout.php
diff --git src/bp-core/classes/class-bp-optout.php src/bp-core/classes/class-bp-optout.php new file mode 100644 index 000000000..6d7725c34
- + 1 <?php 2 /** 3 * BuddyPress Nonmember Opt-out Class 4 * 5 * @package BuddyPress 6 * @subpackage Nonmember Opt-outs 7 * 8 * @since 8.0.0 9 */ 10 11 // Exit if accessed directly. 12 defined( '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 */ 21 class BP_Optout { 22 23 /** 24 * The opt-out ID. 25 * 26 * @since 8.0.0 27 * @access public 28 * @var int 29 */ 30 public $id; 31 32 /** 33 * The hashed email address of the user that wishes to opt out of 34 * communications from this site. 35 * 36 * @since 8.0.0 37 * @access public 38 * @var string 39 */ 40 public $email_address; 41 42 /** 43 * The ID of the user that generated the contact that resulted in the opt-out. 44 * 45 * @since 8.0.0 46 * @access public 47 * @var int 48 */ 49 public $user_id; 50 51 /** 52 * The type of email contact that resulted in the opt-out. 53 * This should be one of the known BP_Email types. 54 * 55 * @since 8.0.0 56 * @access public 57 * @var string 58 */ 59 public $email_type; 60 61 /** 62 * The date the opt-out was last modified. 63 * 64 * @since 8.0.0 65 * @access public 66 * @var string 67 */ 68 public $date_modified; 69 70 /** Public Methods ****************************************************/ 71 72 /** 73 * Constructor method. 74 * 75 * @since 8.0.0 76 * 77 * @param int $id Optional. Provide an ID to access an existing 78 * optout item. 79 */ 80 public function __construct( $id = 0 ) { 81 if ( ! empty( $id ) ) { 82 $this->id = (int) $id; 83 $this->populate(); 84 } 85 } 86 87 /** 88 * Get the opt-outs table name. 89 * 90 * @since 8.0.0 91 * @access public 92 * @return string 93 */ 94 public static function get_table_name() { 95 return buddypress()->members->table_name_optouts; 96 } 97 98 /** 99 * Update or insert opt-out details into the database. 100 * 101 * @since 8.0.0 102 * 103 * @global wpdb $wpdb WordPress database object. 104 * 105 * @return bool True on success, false on failure. 106 */ 107 public function save() { 108 109 // Return value 110 $retval = false; 111 112 // Default data and format 113 $data = array( 114 'email_address_hash' => $this->email_address, 115 'user_id' => $this->user_id, 116 'email_type' => sanitize_key( $this->email_type ), 117 'date_modified' => $this->date_modified, 118 ); 119 $data_format = array( '%s', '%d', '%s', '%s' ); 120 121 /** 122 * Fires before an opt-out is saved. 123 * 124 * @since 8.0.0 125 * 126 * @param BP_Optout object $this Characteristics of the opt-out to be saved. 127 */ 128 do_action_ref_array( 'bp_optout_before_save', array( &$this ) ); 129 130 // Update. 131 if ( ! empty( $this->id ) ) { 132 $result = self::_update( $data, array( 'ID' => $this->id ), $data_format, array( '%d' ) ); 133 // Insert. 134 } else { 135 $result = self::_insert( $data, $data_format ); 136 } 137 138 // Set the opt-out ID if successful. 139 if ( ! empty( $result ) && ! is_wp_error( $result ) ) { 140 global $wpdb; 141 142 $this->id = $wpdb->insert_id; 143 $retval = $wpdb->insert_id; 144 } 145 146 wp_cache_delete( $this->id, 'bp_optouts' ); 147 148 /** 149 * Fires after an optout is saved. 150 * 151 * @since 8.0.0 152 * 153 * @param BP_optout object $this Characteristics of the opt-out just saved. 154 */ 155 do_action_ref_array( 'bp_optout_after_save', array( &$this ) ); 156 157 // Return the result. 158 return $retval; 159 } 160 161 /** 162 * Fetch data for an existing opt-out from the database. 163 * 164 * @since 8.0.0 165 * 166 * @global BuddyPress $bp The one true BuddyPress instance. 167 * @global wpdb $wpdb WordPress database object. 168 */ 169 public function populate() { 170 global $wpdb; 171 $optouts_table_name = $this->get_table_name(); 172 173 // Check cache for optout data. 174 $optout = wp_cache_get( $this->id, 'bp_optouts' ); 175 176 // Cache missed, so query the DB. 177 if ( false === $optout ) { 178 $optout = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$optouts_table_name} WHERE id = %d", $this->id ) ); 179 wp_cache_set( $this->id, $optout, 'bp_optouts' ); 180 } 181 182 // No optout found so set the ID and bail. 183 if ( empty( $optout ) || is_wp_error( $optout ) ) { 184 $this->id = 0; 185 return; 186 } 187 188 $this->email_address = $optout->email_address_hash; 189 $this->user_id = (int) $optout->user_id; 190 $this->email_type = sanitize_key( $optout->email_type ); 191 $this->date_modified = $optout->date_modified; 192 193 } 194 195 /** Protected Static Methods ******************************************/ 196 197 /** 198 * Create an opt-out entry. 199 * 200 * @since 8.0.0 201 * 202 * @param array $data { 203 * Array of optout data, passed to {@link wpdb::insert()}. 204 * @type string $email_address The hashed email address of the user that wishes to opt out of 205 * communications from this site. 206 * @type int $user_id The ID of the user that generated the contact that resulted in the opt-out. 207 * @type string $email_type The type of email contact that resulted in the opt-out. 208 * @type string $date_modified Date the opt-out was last modified. 209 * } 210 * @param array $data_format See {@link wpdb::insert()}. 211 * @return int|false The number of rows inserted, or false on error. 212 */ 213 protected static function _insert( $data = array(), $data_format = array() ) { 214 global $wpdb; 215 // We must hash the email address at insert. 216 $data['email_address_hash'] = wp_hash( $data['email_address_hash'] ); 217 return $wpdb->insert( BP_Optout::get_table_name(), $data, $data_format ); 218 } 219 220 /** 221 * Update opt-outs. 222 * 223 * @since 8.0.0 224 * 225 * @see wpdb::update() for further description of paramater formats. 226 * 227 * @param array $data Array of optout data to update, passed to 228 * {@link wpdb::update()}. Accepts any property of a 229 * BP_optout object. 230 * @param array $where The WHERE params as passed to wpdb::update(). 231 * Typically consists of array( 'ID' => $id ) to specify the ID 232 * of the item being updated. See {@link wpdb::update()}. 233 * @param array $data_format See {@link wpdb::insert()}. 234 * @param array $where_format See {@link wpdb::insert()}. 235 * @return int|false The number of rows updated, or false on error. 236 */ 237 protected static function _update( $data = array(), $where = array(), $data_format = array(), $where_format = array() ) { 238 global $wpdb; 239 240 // Ensure that a passed email address is hashed. 241 if ( ! empty( $data['email_address_hash'] ) && is_email( $data['email_address_hash'] ) ) { 242 $data['email_address_hash'] = wp_hash( $data['email_address_hash'] ); 243 } 244 245 return $wpdb->update( BP_Optout::get_table_name(), $data, $where, $data_format, $where_format ); 246 } 247 248 /** 249 * Delete opt-outs. 250 * 251 * @since 8.0.0 252 * 253 * @see wpdb::update() for further description of paramater formats. 254 * 255 * @param array $where Array of WHERE clauses to filter by, passed to 256 * {@link wpdb::delete()}. Accepts any property of a 257 * BP_optout object. 258 * @param array $where_format See {@link wpdb::insert()}. 259 * @return int|false The number of rows updated, or false on error. 260 */ 261 protected static function _delete( $where = array(), $where_format = array() ) { 262 global $wpdb; 263 return $wpdb->delete( BP_Optout::get_table_name(), $where, $where_format ); 264 } 265 266 /** 267 * Assemble the WHERE clause of a get() SQL statement. 268 * 269 * Used by BP_optout::get() to create its WHERE 270 * clause. 271 * 272 * @since 8.0.0 273 * 274 * @param array $args See {@link BP_optout::get()} for more details. 275 * @return string WHERE clause. 276 */ 277 protected static function get_where_sql( $args = array() ) { 278 global $wpdb; 279 280 $where_conditions = array(); 281 $where = ''; 282 283 // id. 284 if ( false !== $args['id'] ) { 285 $id_in = implode( ',', wp_parse_id_list( $args['id'] ) ); 286 $where_conditions['id'] = "id IN ({$id_in})"; 287 } 288 289 // email_address. 290 if ( ! empty( $args['email_address'] ) ) { 291 if ( ! is_array( $args['email_address'] ) ) { 292 $emails = explode( ',', $args['email_address'] ); 293 } else { 294 $emails = $args['email_address']; 295 } 296 297 $email_clean = array(); 298 foreach ( $emails as $email ) { 299 $email_hash = wp_hash( $email ); 300 $email_clean[] = $wpdb->prepare( '%s', $email_hash ); 301 } 302 303 $email_in = implode( ',', $email_clean ); 304 $where_conditions['email_address'] = "email_address_hash IN ({$email_in})"; 305 } 306 307 // user_id. 308 if ( ! empty( $args['user_id'] ) ) { 309 $user_id_in = implode( ',', wp_parse_id_list( $args['user_id'] ) ); 310 $where_conditions['user_id'] = "user_id IN ({$user_id_in})"; 311 } 312 313 // email_type. 314 if ( ! empty( $args['email_type'] ) ) { 315 if ( ! is_array( $args['email_type'] ) ) { 316 $email_types = explode( ',', $args['email_type'] ); 317 } else { 318 $email_types = $args['email_type']; 319 } 320 321 $et_clean = array(); 322 foreach ( $email_types as $et ) { 323 $et_clean[] = $wpdb->prepare( '%s', sanitize_key( $et ) ); 324 } 325 326 $et_in = implode( ',', $et_clean ); 327 $where_conditions['email_type'] = "email_type IN ({$et_in})"; 328 } 329 330 // search_terms. 331 if ( ! empty( $args['search_terms'] ) ) { 332 // Matching email_address is an exact match because of the hashing. 333 $search_terms_like = wp_hash( $args['search_terms'] ); 334 $where_conditions['search_terms'] = $wpdb->prepare( '( email_address_hash LIKE %s )', $search_terms_like ); 335 } 336 337 // Custom WHERE. 338 if ( ! empty( $where_conditions ) ) { 339 $where = 'WHERE ' . implode( ' AND ', $where_conditions ); 340 } 341 342 return $where; 343 } 344 345 /** 346 * Assemble the ORDER BY clause of a get() SQL statement. 347 * 348 * Used by BP_Optout::get() to create its ORDER BY 349 * clause. 350 * 351 * @since 8.0.0 352 * 353 * @param array $args See {@link BP_optout::get()} for more details. 354 * @return string ORDER BY clause. 355 */ 356 protected static function get_order_by_sql( $args = array() ) { 357 358 $conditions = array(); 359 $retval = ''; 360 361 // Order by. 362 if ( ! empty( $args['order_by'] ) ) { 363 $order_by_clean = array(); 364 $columns = array( 'id', 'email_address_hash', 'user_id', 'email_type', 'date_modified' ); 365 foreach ( (array) $args['order_by'] as $key => $value ) { 366 if ( in_array( $value, $columns, true ) ) { 367 $order_by_clean[] = $value; 368 } 369 } 370 if ( ! empty( $order_by_clean ) ) { 371 $order_by = implode( ', ', $order_by_clean ); 372 $conditions['order_by'] = "{$order_by}"; 373 } 374 } 375 376 // Sort order direction. 377 if ( ! empty( $args['sort_order'] ) ) { 378 $sort_order = bp_esc_sql_order( $args['sort_order'] ); 379 $conditions['sort_order'] = "{$sort_order}"; 380 } 381 382 // Custom ORDER BY. 383 if ( ! empty( $conditions['order_by'] ) ) { 384 $retval = 'ORDER BY ' . implode( ' ', $conditions ); 385 } 386 387 return $retval; 388 } 389 390 /** 391 * Assemble the LIMIT clause of a get() SQL statement. 392 * 393 * Used by BP_Optout::get() to create its LIMIT clause. 394 * 395 * @since 8.0.0 396 * 397 * @param array $args See {@link BP_optout::get()} for more details. 398 * @return string LIMIT clause. 399 */ 400 protected static function get_paged_sql( $args = array() ) { 401 global $wpdb; 402 403 // Setup local variable. 404 $retval = ''; 405 406 // Custom LIMIT. 407 if ( ! empty( $args['page'] ) && ! empty( $args['per_page'] ) ) { 408 $page = absint( $args['page'] ); 409 $per_page = absint( $args['per_page'] ); 410 $offset = $per_page * ( $page - 1 ); 411 $retval = $wpdb->prepare( "LIMIT %d, %d", $offset, $per_page ); 412 } 413 414 return $retval; 415 } 416 417 /** 418 * Assemble query clauses, based on arguments, to pass to $wpdb methods. 419 * 420 * The insert(), update(), and delete() methods of {@link wpdb} expect 421 * arguments of the following forms: 422 * 423 * - associative arrays whose key/value pairs are column => value, to 424 * be used in WHERE, SET, or VALUES clauses 425 * - arrays of "formats", which tell $wpdb->prepare() which type of 426 * value to expect when sanitizing (eg, array( '%s', '%d' )) 427 * 428 * This utility method can be used to assemble both kinds of params, 429 * out of a single set of associative array arguments, such as: 430 * 431 * $args = array( 432 * 'user_id' => 4, 433 * 'email_type' => 'type_string', 434 * ); 435 * 436 * This will be converted to: 437 * 438 * array( 439 * 'data' => array( 440 * 'user_id' => 4, 441 * 'email_type' => 'type_string', 442 * ), 443 * 'format' => array( 444 * '%d', 445 * '%s', 446 * ), 447 * ) 448 * 449 * which can easily be passed as arguments to the $wpdb methods. 450 * 451 * @since 8.0.0 452 * 453 * @param array $args Associative array of filter arguments. 454 * See {@BP_optout::get()} for a breakdown. 455 * @return array Associative array of 'data' and 'format' args. 456 */ 457 protected static function get_query_clauses( $args = array() ) { 458 $where_clauses = array( 459 'data' => array(), 460 'format' => array(), 461 ); 462 463 // id. 464 if ( ! empty( $args['id'] ) ) { 465 $where_clauses['data']['id'] = absint( $args['id'] ); 466 $where_clauses['format'][] = '%d'; 467 } 468 469 // email_address. 470 if ( ! empty( $args['email_address'] ) ) { 471 $where_clauses['data']['email_address_hash'] = $args['email_address']; 472 $where_clauses['format'][] = '%s'; 473 } 474 475 // user_id. 476 if ( ! empty( $args['user_id'] ) ) { 477 $where_clauses['data']['user_id'] = absint( $args['user_id'] ); 478 $where_clauses['format'][] = '%d'; 479 } 480 481 // email_type. 482 if ( ! empty( $args['email_type'] ) ) { 483 $where_clauses['data']['email_type'] = $args['email_type']; 484 $where_clauses['format'][] = '%s'; 485 } 486 487 return $where_clauses; 488 } 489 490 /** Public Static Methods *********************************************/ 491 492 /** 493 * Get opt-outs, based on provided filter parameters. 494 * 495 * @since 8.0.0 496 * 497 * @param array $args { 498 * Associative array of arguments. All arguments but $page and 499 * $per_page can be treated as filter values for get_where_sql() 500 * and get_query_clauses(). All items are optional. 501 * @type int|array $id ID of opt-out. 502 * Can be an array of IDs. 503 * @type string|array $email_address Email address of users who have opted out 504 * being queried. Can be an array of addresses. 505 * @type int|array $user_id ID of user whose communication prompted the 506 * opt-out. Can be an array of IDs. 507 * @type string|array $email_type Name of the emil type to filter by. 508 * Can be an array of email types. 509 * @type string $search_terms Term to match against email_address field. 510 * @type string $order_by Database column to order by. 511 * @type string $sort_order Either 'ASC' or 'DESC'. 512 * @type int $page Number of the current page of results. 513 * Default: false (no pagination, 514 * all items). 515 * @type int $per_page Number of items to show per page. 516 * Default: false (no pagination, 517 * all items). 518 * @type string $fields Which fields to return. Specify 'email_addresses' to 519 * fetch a list of opt-out email_addresses. 520 * Specify 'user_ids' to 521 * fetch a list of opt-out user_ids. 522 * Specify 'ids' to fetch a list of opt-out IDs. 523 * Default: 'all' (return BP_Optout objects). 524 * } 525 * 526 * @return array BP_Optout objects | IDs of found opt-outs | Email addresses of matches. 527 */ 528 public static function get( $args = array() ) { 529 global $wpdb; 530 $optouts_table_name = BP_Optout::get_table_name(); 531 532 // Parse the arguments. 533 $r = bp_parse_args( $args, array( 534 'id' => false, 535 'email_address' => false, 536 'user_id' => false, 537 'email_type' => false, 538 'search_terms' => '', 539 'order_by' => false, 540 'sort_order' => false, 541 'page' => false, 542 'per_page' => false, 543 'fields' => 'all', 544 ), 'bp_optout_get' ); 545 546 $sql = array( 547 'select' => "SELECT", 548 'fields' => '', 549 'from' => "FROM {$optouts_table_name} o", 550 'where' => '', 551 'orderby' => '', 552 'pagination' => '', 553 ); 554 555 if ( 'user_ids' === $r['fields'] ) { 556 $sql['fields'] = "DISTINCT o.user_id"; 557 } else if ( 'email_addresses' === $r['fields'] ) { 558 $sql['fields'] = "DISTINCT o.email_address_hash"; 559 } else { 560 $sql['fields'] = 'DISTINCT o.id'; 561 } 562 563 // WHERE. 564 $sql['where'] = self::get_where_sql( array( 565 'id' => $r['id'], 566 'email_address' => $r['email_address'], 567 'user_id' => $r['user_id'], 568 'email_type' => $r['email_type'], 569 'search_terms' => $r['search_terms'], 570 ) ); 571 572 // ORDER BY. 573 $sql['orderby'] = self::get_order_by_sql( array( 574 'order_by' => $r['order_by'], 575 'sort_order' => $r['sort_order'] 576 ) ); 577 578 // LIMIT %d, %d. 579 $sql['pagination'] = self::get_paged_sql( array( 580 'page' => $r['page'], 581 'per_page' => $r['per_page'], 582 ) ); 583 584 $paged_optouts_sql = "{$sql['select']} {$sql['fields']} {$sql['from']} {$sql['where']} {$sql['orderby']} {$sql['pagination']}"; 585 586 /** 587 * Filters the pagination SQL statement. 588 * 589 * @since 8.0.0 590 * 591 * @param string $value Concatenated SQL statement. 592 * @param array $sql Array of SQL parts before concatenation. 593 * @param array $r Array of parsed arguments for the get method. 594 */ 595 $paged_optouts_sql = apply_filters( 'bp_optouts_get_paged_optouts_sql', $paged_optouts_sql, $sql, $r ); 596 597 $cached = bp_core_get_incremented_cache( $paged_optouts_sql, 'bp_optouts' ); 598 if ( false === $cached ) { 599 $paged_optout_ids = $wpdb->get_col( $paged_optouts_sql ); 600 bp_core_set_incremented_cache( $paged_optouts_sql, 'bp_optouts', $paged_optout_ids ); 601 } else { 602 $paged_optout_ids = $cached; 603 } 604 605 // Special return format cases. 606 if ( in_array( $r['fields'], array( 'ids', 'user_ids' ), true ) ) { 607 // We only want the field that was found. 608 return array_map( 'intval', $paged_optout_ids ); 609 } else if ( 'email_addresses' === $r['fields'] ) { 610 return $paged_optout_ids; 611 } 612 613 $uncached_ids = bp_get_non_cached_ids( $paged_optout_ids, 'bp_optouts' ); 614 if ( $uncached_ids ) { 615 $ids_sql = implode( ',', array_map( 'intval', $uncached_ids ) ); 616 $data_objects = $wpdb->get_results( "SELECT o.* FROM {$optouts_table_name} o WHERE o.id IN ({$ids_sql})" ); 617 foreach ( $data_objects as $data_object ) { 618 wp_cache_set( $data_object->id, $data_object, 'bp_optouts' ); 619 } 620 } 621 622 $paged_optouts = array(); 623 foreach ( $paged_optout_ids as $paged_optout_id ) { 624 $paged_optouts[] = new BP_optout( $paged_optout_id ); 625 } 626 627 return $paged_optouts; 628 } 629 630 /** 631 * Get a count of total optouts matching a set of arguments. 632 * 633 * @since 8.0.0 634 * 635 * @see BP_optout::get() for a description of 636 * arguments. 637 * 638 * @param array $args See {@link BP_optout::get()}. 639 * @return int Count of located items. 640 */ 641 public static function get_total_count( $args ) { 642 global $wpdb; 643 $optouts_table_name = BP_Optout::get_table_name(); 644 645 // Parse the arguments. 646 $r = bp_parse_args( $args, array( 647 'id' => false, 648 'email_address' => false, 649 'user_id' => false, 650 'email_type' => false, 651 'search_terms' => '', 652 'order_by' => false, 653 'sort_order' => false, 654 'page' => false, 655 'per_page' => false, 656 'fields' => 'all', 657 ), 'bp_optout_get_total_count' ); 658 659 // Build the query 660 $select_sql = "SELECT COUNT(*)"; 661 $from_sql = "FROM {$optouts_table_name}"; 662 $where_sql = self::get_where_sql( $r ); 663 $sql = "{$select_sql} {$from_sql} {$where_sql}"; 664 665 // Return the queried results 666 return $wpdb->get_var( $sql ); 667 } 668 669 /** 670 * Update optouts. 671 * 672 * @since 8.0.0 673 * 674 * @see BP_optout::get() for a description of 675 * accepted update/where arguments. 676 * 677 * @param array $update_args Associative array of fields to update, 678 * and the values to update them to. Of the format 679 * array( 'user_id' => 4, 'email_address' => 'bar@foo.com', ). 680 * @param array $where_args Associative array of columns/values, to 681 * determine which rows should be updated. Of the format 682 * array( 'user_id' => 7, 'email_address' => 'bar@foo.com', ). 683 * @return int|bool Number of rows updated on success, false on failure. 684 */ 685 public static function update( $update_args = array(), $where_args = array() ) { 686 $update = self::get_query_clauses( $update_args ); 687 $where = self::get_query_clauses( $where_args ); 688 689 /** 690 * Fires before an opt-out is updated. 691 * 692 * @since 8.0.0 693 * 694 * @param array $where_args Associative array of columns/values describing 695 * optouts about to be deleted. 696 * @param array $update_args Array of new values. 697 */ 698 do_action( 'bp_optout_before_update', $where_args, $update_args ); 699 700 $retval = self::_update( $update['data'], $where['data'], $update['format'], $where['format'] ); 701 702 // Clear matching items from the cache. 703 $cache_args = $where_args; 704 $cache_args['fields'] = 'ids'; 705 $maybe_cached_ids = self::get( $cache_args ); 706 foreach ( $maybe_cached_ids as $invite_id ) { 707 wp_cache_delete( $invite_id, 'bp_optouts' ); 708 } 709 710 /** 711 * Fires after an opt-out is updated. 712 * 713 * @since 8.0.0 714 * 715 * @param array $where_args Associative array of columns/values describing 716 * optouts about to be deleted. 717 * @param array $update_args Array of new values. 718 */ 719 do_action( 'bp_optout_after_update', $where_args, $update_args ); 720 721 return $retval; 722 } 723 724 /** 725 * Delete opt-outs. 726 * 727 * @since 8.0.0 728 * 729 * @see BP_optout::get() for a description of 730 * accepted where arguments. 731 * 732 * @param array $args Associative array of columns/values, to determine 733 * which rows should be deleted. Of the format 734 * array( 'user_id' => 7, 'email_address' => 'bar@foo.com', ). 735 * @return int|bool Number of rows deleted on success, false on failure. 736 */ 737 public static function delete( $args = array() ) { 738 $where = self::get_query_clauses( $args ); 739 740 /** 741 * Fires before an optout is deleted. 742 * 743 * @since 8.0.0 744 * 745 * @param array $args Characteristics of the optouts to be deleted. 746 */ 747 do_action( 'bp_optout_before_delete', $args ); 748 749 // Clear matching items from the cache. 750 $cache_args = $args; 751 $cache_args['fields'] = 'ids'; 752 $maybe_cached_ids = self::get( $cache_args ); 753 foreach ( $maybe_cached_ids as $invite_id ) { 754 wp_cache_delete( $invite_id, 'bp_optouts' ); 755 } 756 757 $retval = self::_delete( $where['data'], $where['format'] ); 758 759 /** 760 * Fires after an optout is deleted. 761 * 762 * @since 8.0.0 763 * 764 * @param array $args Characteristics of the optouts just deleted. 765 */ 766 do_action( 'bp_optout_after_delete', $args ); 767 768 return $retval; 769 } 770 771 /** Convenience methods ***********************************************/ 772 773 /** 774 * Check whether an invitation exists matching the passed arguments. 775 * 776 * @since 5.0.0 777 * 778 * @see BP_Optout::get() for a description of accepted parameters. 779 * 780 * @return int|bool ID of first found invitation or false if none found. 781 */ 782 public function optout_exists( $args = array() ) { 783 $exists = false; 784 785 $args['fields'] = 'ids'; 786 $optouts = BP_Optout::get( $args ); 787 if ( $optouts ) { 788 $exists = current( $optouts ); 789 } 790 return $exists; 791 } 792 793 /** 794 * Delete a single optout by ID. 795 * 796 * @since 8.0.0 797 * 798 * @see BP_optout::delete() for explanation of 799 * return value. 800 * 801 * @param int $id ID of the optout item to be deleted. 802 * @return bool True on success, false on failure. 803 */ 804 public static function delete_by_id( $id ) { 805 return self::delete( array( 806 'id' => $id, 807 ) ); 808 } 809 810 } -
new file src/bp-core/classes/class-bp-optouts-list-table.php
diff --git src/bp-core/classes/class-bp-optouts-list-table.php src/bp-core/classes/class-bp-optouts-list-table.php new file mode 100644 index 000000000..7615583d8
- + 1 <?php 2 /** 3 * BuddyPress Opt-outs List Table class. 4 * 5 * @package BuddyPress 6 * @subpackage CoreAdminClasses 7 * @since 8.0.0 8 */ 9 10 // Exit if accessed directly. 11 defined( 'ABSPATH' ) || exit; 12 13 /** 14 * List table class for nonmember opt-outs admin page. 15 * 16 * @since 8.0.0 17 */ 18 class BP_Optouts_List_Table extends WP_Users_List_Table { 19 20 /** 21 * Opt-out count. 22 * 23 * @since 8.0.0 24 * 25 * @var int 26 */ 27 public $total_items = 0; 28 29 /** 30 * Constructor. 31 * 32 * @since 8.0.0 33 */ 34 public function __construct() { 35 // Define singular and plural labels, as well as whether we support AJAX. 36 parent::__construct( array( 37 'ajax' => false, 38 'plural' => 'optouts', 39 'singular' => 'optout', 40 'screen' => get_current_screen()->id, 41 ) ); 42 } 43 44 /** 45 * Set up items for display in the list table. 46 * 47 * Handles filtering of data, sorting, pagination, and any other data 48 * manipulation required prior to rendering. 49 * 50 * @since 8.0.0 51 */ 52 public function prepare_items() { 53 global $usersearch; 54 55 $search = isset( $_REQUEST['s'] ) ? $_REQUEST['s'] : ''; 56 $per_page = $this->get_items_per_page( str_replace( '-', '_', "{$this->screen->id}_per_page" ) ); 57 $paged = $this->get_pagenum(); 58 59 $args = array( 60 'search_terms' => $search, 61 'order_by' => 'date_modified', 62 'sort_order' => 'DESC', 63 'page' => $paged, 64 'per_page' => $per_page, 65 ); 66 67 if ( isset( $_REQUEST['orderby'] ) ) { 68 $args['order_by'] = $_REQUEST['orderby']; 69 } 70 71 if ( isset( $_REQUEST['order'] ) ) { 72 $args['sort_order'] = $_REQUEST['order']; 73 } 74 75 $this->items = bp_get_optouts( $args ); 76 $optouts_class = new BP_Optout(); 77 $this->total_items = $optouts_class->get_total_count( $args ); 78 79 $this->set_pagination_args( array( 80 'total_items' => $this->total_items, 81 'per_page' => $per_page, 82 ) ); 83 } 84 85 /** 86 * Get the list of views available on this table. 87 * 88 * @since 8.0.0 89 */ 90 public function views() { 91 if ( is_multisite() && bp_core_do_network_admin() ) { 92 $tools_parent = 'network-tools'; 93 } else { 94 $tools_parent = 'tools.php'; 95 } 96 97 $url_base = add_query_arg( 98 array( 99 'page' => 'bp-optouts', 100 ), 101 bp_get_admin_url( $tools_parent ) 102 ); 103 ?> 104 105 <h2 class="screen-reader-text"><?php 106 /* translators: accessibility text */ 107 _e( 'Filter opt-outs list', 'buddypress' ); 108 ?></h2> 109 <ul class="subsubsub"> 110 <?php 111 /** 112 * Fires inside listing of views so plugins can add their own. 113 * 114 * @since 8.0.0 115 * 116 * @param string $url_base Current URL base for view. 117 * @param array $active_filters Current filters being requested. 118 */ 119 do_action( 'bp_optouts_list_table_get_views', $url_base, $this->active_filters ); ?> 120 </ul> 121 <?php 122 } 123 124 /** 125 * Get rid of the extra nav. 126 * 127 * WP_Users_List_Table will add an extra nav to change user's role. 128 * As we're dealing with opt-outs, we don't need this. 129 * 130 * @since 8.0.0 131 * 132 * @param array $which Current table nav item. 133 */ 134 public function extra_tablenav( $which ) { 135 return; 136 } 137 138 /** 139 * Specific opt-out columns. 140 * 141 * @since 8.0.0 142 * 143 * @return array 144 */ 145 public function get_columns() { 146 /** 147 * Filters the nonmember opt-outs columns. 148 * 149 * @since 8.0.0 150 * 151 * @param array $value Array of columns to display. 152 */ 153 return apply_filters( 'bp_optouts_list_columns', array( 154 'cb' => '<input type="checkbox" />', 155 'email_address' => __( 'Email Address Hash', 'buddypress' ), 156 'username' => __( 'Email Sender', 'buddypress' ), 157 'user_registered' => __( 'Email Sender Registered', 'buddypress' ), 158 'email_type' => __( 'Email Type', 'buddypress' ), 159 'email_type_description' => __( 'Email Description', 'buddypress' ), 160 'optout_date_modified' => __( 'Date Modified', 'buddypress' ), 161 ) ); 162 } 163 164 /** 165 * Specific bulk actions for opt-outs. 166 * 167 * @since 8.0.0 168 */ 169 public function get_bulk_actions() { 170 if ( current_user_can( 'delete_users' ) ) { 171 $actions['delete'] = _x( 'Delete', 'Optout database record action', 'buddypress' ); 172 } 173 174 return $actions; 175 } 176 177 /** 178 * The text shown when no items are found. 179 * 180 * Nice job, clean sheet! 181 * 182 * @since 8.0.0 183 */ 184 public function no_items() { 185 esc_html_e( 'No opt-outs found.', 'buddypress' ); 186 } 187 188 /** 189 * The columns opt-outs can be reordered by. 190 * 191 * @since 8.0.0 192 */ 193 public function get_sortable_columns() { 194 return array( 195 'email_address' => 'email_address_hash', 196 'username' => 'user_id', 197 'email_type' => 'email_type', 198 'optout_date_modified' => 'date_modified', 199 ); 200 } 201 202 /** 203 * Display opt-out rows. 204 * 205 * @since 8.0.0 206 */ 207 public function display_rows() { 208 $style = ''; 209 foreach ( $this->items as $optout ) { 210 $style = ( ' class="alternate"' == $style ) ? '' : ' class="alternate"'; 211 echo "\n\t" . $this->single_row( $optout, $style ); 212 } 213 } 214 215 /** 216 * Display an opt-out row. 217 * 218 * @since 8.0.0 219 * 220 * @see WP_List_Table::single_row() for explanation of params. 221 * 222 * @param BP_Optout $optout BP_Optout object. 223 * @param string $style Styles for the row. 224 * @param string $role Role to be assigned to user. 225 * @param int $numposts Number of posts. 226 * @return void 227 */ 228 public function single_row( $optout = null, $style = '', $role = '', $numposts = 0 ) { 229 echo '<tr' . $style . ' id="optout-' . esc_attr( $optout->id ) . '">'; 230 echo $this->single_row_columns( $optout ); 231 echo '</tr>'; 232 } 233 234 /** 235 * Markup for the checkbox used to select items for bulk actions. 236 * 237 * @since 8.0.0 238 * 239 * @param BP_Optout $optout BP_Optout object. 240 */ 241 public function column_cb( $optout = null ) { 242 ?> 243 <label class="screen-reader-text" for="optout_<?php echo intval( $optout->id ); ?>"><?php 244 /* translators: accessibility text */ 245 printf( esc_html__( 'Select opt-out request: %s', 'buddypress' ), $optout->id ); 246 ?></label> 247 <input type="checkbox" id="optout_<?php echo intval( $optout->id ) ?>" name="optout_ids[]" value="<?php echo esc_attr( $optout->id ) ?>" /> 248 <?php 249 } 250 251 /** 252 * Markup for the checkbox used to select items for bulk actions. 253 * 254 * @since 8.0.0 255 * 256 * @param BP_Optout $optout BP_Optout object. 257 */ 258 public function column_email_address( $optout = null ) { 259 printf( '<a href="mailto:%1$s">%2$s</a>', esc_attr( $optout->email_address ), esc_html( $optout->email_address ) ); 260 261 $actions = array(); 262 263 if ( is_network_admin() ) { 264 $form_url = network_admin_url( 'network-tools' ); 265 } else { 266 $form_url = bp_get_admin_url( 'tools.php' ); 267 } 268 269 // Delete link. 270 $delete_link = add_query_arg( 271 array( 272 'page' => 'bp-optouts', 273 'optout_id' => $optout->id, 274 'action' => 'delete', 275 ), 276 $form_url 277 ); 278 $actions['delete'] = sprintf( '<a href="%1$s" class="delete">%2$s</a>', esc_url( $delete_link ), __( 'Delete', 'buddypress' ) ); 279 280 /** 281 * Filters the row actions for each opt-out in list. 282 * 283 * @since 8.0.0 284 * 285 * @param array $actions Array of actions and corresponding links. 286 * @param object $optout The BP_Optout. 287 */ 288 $actions = apply_filters( 'bp_optouts_management_row_actions', $actions, $optout ); 289 290 echo $this->row_actions( $actions ); 291 } 292 293 /** 294 * The inviter/site member who sent the email that prompted the opt-out. 295 * 296 * @since 8.0.0 297 * 298 * @param BP_Optout $optout BP_Optout object. 299 */ 300 public function column_username( $optout = null ) { 301 $avatar = get_avatar( $optout->user_id, 32 ); 302 $inviter = get_user_by( 'id', $optout->user_id ); 303 if ( ! $inviter ) { 304 return; 305 } 306 $user_link = bp_core_get_user_domain( $optout->user_id ); 307 echo $avatar . sprintf( '<strong><a href="%1$s" class="edit">%2$s</a></strong><br/>', esc_url( $user_link ), $inviter->user_login ); 308 } 309 310 /** 311 * Display registration date of user whose communication prompted opt-out. 312 * 313 * @since 8.0.0 314 * 315 * @param BP_Optout $optout BP_Optout object. 316 */ 317 public function column_user_registered( $optout = null ) { 318 $inviter = get_user_by( 'id', $optout->user_id ); 319 if ( ! $inviter ) { 320 return; 321 } 322 echo esc_html( mysql2date( 'Y/m/d g:i:s a', $inviter->user_registered ) ); 323 } 324 325 /** 326 * Display type of email that prompted opt-out. 327 * 328 * @since 8.0.0 329 * 330 * @param BP_Optout $optout BP_Optout object. 331 */ 332 public function column_email_type( $optout = null ) { 333 echo esc_html( $optout->email_type ); 334 } 335 336 /** 337 * Display description of bp-email-type that prompted opt-out. 338 * 339 * @since 8.0.0 340 * 341 * @param BP_Optout $optout BP_Optout object. 342 */ 343 public function column_email_type_description( $optout = null ) { 344 $type_term = get_term_by( 'slug', $optout->email_type, 'bp-email-type' ); 345 if ( $type_term ) { 346 echo esc_html( $type_term->description ); 347 } 348 349 } 350 351 /** 352 * Display opt-out date. 353 * 354 * @since 8.0.0 355 * 356 * @param BP_Optout $optout BP_Optout object. 357 */ 358 public function column_optout_date_modified( $optout = null ) { 359 echo esc_html( mysql2date( 'Y/m/d g:i:s a', $optout->date_modified ) ); 360 } 361 362 /** 363 * Allow plugins to add custom columns. 364 * 365 * @since 8.0.0 366 * 367 * @param BP_Optout $optout BP_Optout object. 368 * @param string $column_name The column name. 369 * @return string 370 */ 371 function column_default( $optout = null, $column_name = '' ) { 372 373 /** 374 * Filters the single site custom columns for plugins. 375 * 376 * @since 8.0.0 377 * 378 * @param string $column_name The column name. 379 * @param BP_Optout $optout BP_Optout object. 380 */ 381 return apply_filters( 'bp_optouts_management_custom_column', '', $column_name, $optout ); 382 } 383 } -
src/bp-members/classes/class-bp-members-admin.php
diff --git src/bp-members/classes/class-bp-members-admin.php src/bp-members/classes/class-bp-members-admin.php index c999cb923..17f73cb37 100644
class BP_Members_Admin { 488 488 'bp-signups', 489 489 array( $this, 'signups_admin' ) 490 490 ); 491 491 492 } 492 493 493 494 $edit_page = 'user-edit'; … … class BP_Members_Admin { 509 510 $this->user_page .= '-network'; 510 511 $this->users_page .= '-network'; 511 512 $this->signups_page .= '-network'; 513 514 $this->members_optouts_page .= '-network'; 512 515 } 513 516 514 517 // Setup the screen ID's. … … class BP_Members_Admin { 2564 2567 2565 2568 return $value; 2566 2569 } 2570 2567 2571 } 2568 2572 endif; // 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 { 178 178 'global_tables' => array( 179 179 'table_name_invitations' => bp_core_get_table_prefix() . 'bp_invitations', 180 180 'table_name_last_activity' => bp_core_get_table_prefix() . 'bp_activity', 181 'table_name_optouts' => bp_core_get_table_prefix() . 'bp_optouts', 181 182 'table_name_signups' => $wpdb->base_prefix . 'signups', // Signups is a global WordPress table. 182 183 ) 183 184 ); -
src/class-buddypress.php
diff --git src/class-buddypress.php src/class-buddypress.php index 3dd1d483c..1af41585b 100644
class BuddyPress { 597 597 'BP_REST_Components_Endpoint' => 'core', 598 598 'BP_REST_Attachments' => 'core', 599 599 'BP_Admin_Types' => 'core', 600 'BP_Optout' => 'core', 601 'BP_Optouts_List_Table' => 'core', 600 602 601 603 'BP_Core_Friends_Widget' => 'friends', 602 604 '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 }