Skip to:
Content

BuddyPress.org

Ticket #8139: 8139.08.patch

File 8139.08.patch, 199.5 KB (added by dcavins, 3 months ago)

Fix tests, and new warnings in BP_Email::validate change.

  • 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 7749dd449..1b2642448 100644
    function bp_core_modify_admin_menu_highlight() { 
    8585                $submenu_file = $plugin_page;
    8686        }
    8787
    88         // Keep the BuddyPress tools menu highlighted.
    89         if ( 'bp-optouts' === $plugin_page ) {
     88        // Keep the BuddyPress tools menu highlighted when using a tools tab.
     89        if ( 'bp-optouts' === $plugin_page || 'bp-members-invitations' === $plugin_page ) {
    9090                $submenu_file = 'bp-tools';
    9191        }
    9292}
    function bp_core_activation_notice() { 
    284284
    285285        // Activate and Register are special cases. They are not components but they need WP pages.
    286286        // If user registration is disabled, we can skip this step.
    287         if ( bp_get_signup_allowed() ) {
     287        if ( bp_get_signup_allowed() || bp_get_members_invitations_allowed() ) {
    288288                $wp_page_components[] = array(
    289289                        'id'   => 'activate',
    290290                        'name' => __( 'Activate', 'buddypress' ),
    function bp_core_get_admin_tabs( $active_tab = '', $context = 'settings' ) { 
    479479                                'name' => __( 'Repair', 'buddypress' ),
    480480                        ),
    481481                        '1' => array(
     482                                'href' => bp_get_admin_url( add_query_arg( array( 'page' => 'bp-members-invitations' ), $tools_page ) ),
     483                                'name' => __( 'Manage Invitations', 'buddypress' ),
     484                        ),
     485                        '2' => array(
    482486                                'href' => bp_get_admin_url( add_query_arg( array( 'page' => 'bp-optouts' ), $tools_page ) ),
    483487                                'name' => __( 'Manage Opt-outs', 'buddypress' ),
    484488                        ),
  • 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
    index d47cf79de..95d789a08 100644
    function bp_core_get_optouts_notice() { 
    174174                );
    175175
    176176                if ( ! empty( $_REQUEST['deleted'] ) ) {
     177                        $deleted            = absint( $_REQUEST['deleted'] );
    177178                        $notice['message'] .= sprintf(
    178                                 /* translators: %s: number of deleted optouts */
    179                                 _nx( '%s opt-out successfully deleted!', '%s opt-outs successfully deleted!',
    180                                  absint( $_REQUEST['deleted'] ),
    181                                  'nonmembers opt-out deleted',
    182                                  'buddypress'
     179                                _nx(
     180                                        /* translators: %s: number of deleted optouts */
     181                                        '%s opt-out successfully deleted!', '%s opt-outs successfully deleted!',
     182                                        $deleted,
     183                                        'nonmembers opt-out deleted',
     184                                        'buddypress'
    183185                                ),
    184186                                number_format_i18n( absint( $_REQUEST['deleted'] ) )
    185187                        );
    186188                }
    187189
    188190                if ( ! empty( $_REQUEST['notdeleted'] ) ) {
     191                        $notdeleted         = absint( $_REQUEST['notdeleted'] );
    189192                        $notice['message'] .= sprintf(
    190                                 /* translators: %s: number of optouts that failed to be deleted */
    191                                 _nx( '%s opt-out was not deleted.', '%s opt-outs were not deleted.',
    192                                  absint( $_REQUEST['notdeleted'] ),
    193                                  'nonmembers opt-out not deleted',
    194                                  'buddypress'
     193                                _nx(
     194                                        /* translators: %s: number of optouts that failed to be deleted */
     195                                        '%s opt-out was not deleted.', '%s opt-outs were not deleted.',
     196                                        $notdeleted,
     197                                        'nonmembers opt-out not deleted',
     198                                        'buddypress'
    195199                                ),
    196                                 number_format_i18n( absint( $_REQUEST['notdeleted'] ) )
     200                                number_format_i18n( $notdeleted )
    197201                        );
    198202
    199203                        if ( empty( $_REQUEST['deleted'] ) ) {
  • src/bp-core/admin/bp-core-admin-settings.php

    diff --git src/bp-core/admin/bp-core-admin-settings.php src/bp-core/admin/bp-core-admin-settings.php
    index c09f809ca..b37b914ff 100644
    function bp_admin_setting_callback_cover_image_uploads() { 
    182182<?php
    183183}
    184184
     185/**
     186 * Allow members to invite non-members to the network.
     187 *
     188 * @since 8.0.0
     189 */
     190function bp_admin_setting_callback_members_invitations() {
     191?>
     192        <input id="bp-enable-members-invitations" name="bp-enable-members-invitations" type="checkbox" value="1" <?php checked( bp_get_members_invitations_allowed() ); ?> />
     193        <label for="bp-enable-members-invitations"><?php _e( 'Allow registered members to invite people to join this network', 'buddypress' ); ?></label>
     194        <?php if ( ! bp_get_signup_allowed() ) : ?>
     195                <p class="description"><?php _e( 'Public registration is currently disabled. However, invitees will still be able to register if network invitations are enabled.', 'buddypress' ); ?></p>
     196        <?php endif; ?>
     197        <?php
     198        /**
     199         * Fires after the output of the invitations settings section.
     200         *
     201         * @since 8.0.0
     202         */
     203        do_action( 'bp_admin_settings_after_members_invitations' );
     204}
     205
    185206/** XProfile ******************************************************************/
    186207
    187208/**
  • src/bp-core/admin/bp-core-admin-slugs.php

    diff --git src/bp-core/admin/bp-core-admin-slugs.php src/bp-core/admin/bp-core-admin-slugs.php
    index 8fb9b9a40..52ef6e36c 100644
    function bp_core_admin_slugs_options() { 
    187187
    188188                <h3><?php _e( 'Registration', 'buddypress' ); ?></h3>
    189189
    190                 <?php if ( bp_get_signup_allowed() ) : ?>
     190                <?php if ( bp_get_signup_allowed() || bp_get_members_invitations_allowed() ) : ?>
    191191                        <p><?php _e( 'Associate WordPress Pages with the following BuddyPress Registration pages.', 'buddypress' ); ?></p>
    192192                <?php else : ?>
    193193                        <?php if ( is_multisite() ) : ?>
    function bp_core_admin_slugs_options() { 
    210210                <table class="form-table">
    211211                        <tbody>
    212212
    213                                 <?php if ( bp_get_signup_allowed() ) : foreach ( $static_pages as $name => $label ) : ?>
     213                                <?php if ( bp_get_signup_allowed() || bp_get_members_invitations_allowed() ) : foreach ( $static_pages as $name => $label ) : ?>
    214214
    215215                                        <tr valign="top">
    216216                                                <th scope="row">
  • src/bp-core/admin/bp-core-admin-tools.php

    diff --git src/bp-core/admin/bp-core-admin-tools.php src/bp-core/admin/bp-core-admin-tools.php
    index f93549295..bd2a2d3f8 100644
    function bp_core_admin_available_tools_intro() { 
    557557                                );
    558558                                ?>
    559559                        </dd>
     560
     561                        <dt><?php esc_html_e( 'Manage Invitations', 'buddypress' ) ?></dt>
     562                        <dd>
     563                                <?php esc_html_e( 'When enabled, BuddyPress allows your users to invite nonmembers to join your site.', 'buddypress' ); ?>
     564                                <?php
     565                                $url = add_query_arg( 'page', 'bp-members-invitations', bp_get_admin_url( $page ) );
     566                                printf(
     567                                        /* translators: %s: the link to the BuddyPress Invitations management tool screen */
     568                                        esc_html_x( 'Visit %s to manage your site&rsquo;s invitations.', 'buddypress invitations tool intro', 'buddypress' ),
     569                                        '<a href="' . esc_url( $url ) . '">' . esc_html__( 'Invitations', 'buddypress' ) . '</a>'
     570                                );
     571                                ?>
     572                        </dd>
     573
    560574                        <dt><?php esc_html_e( 'Manage Opt-outs', 'buddypress' ) ?></dt>
    561575                        <dd>
    562576                                <?php esc_html_e( 'BuddyPress stores opt-out requests from people who are not members of this site, but have been contacted via communication from this site, and wish to opt-out from future communication.', 'buddypress' ); ?>
    563577                                <?php
    564578                                $url = add_query_arg( 'page', 'bp-optouts', bp_get_admin_url( $page ) );
    565579                                printf(
    566                                         /* translators: %s: the link to the BuddyPress Nonmember Opt-outs */
     580                                        /* translators: %s: the link to the BuddyPress Nonmember Opt-outs management tool screen */
    567581                                        esc_html_x( 'Visit %s to manage your site&rsquo;s opt-out requests.', 'buddypress opt-outs intro', 'buddypress' ),
    568582                                        '<a href="' . esc_url( $url ) . '">' . esc_html__( 'Nonmember Opt-outs', 'buddypress' ) . '</a>'
    569583                                );
  • src/bp-core/admin/css/common-rtl.css

    diff --git src/bp-core/admin/css/common-rtl.css src/bp-core/admin/css/common-rtl.css
    index d460dc8d2..1bbd8508a 100644
    body.tools-php .bp-tools dd { 
    317317        margin: 0;
    318318}
    319319
     320body.tools_page_bp-members-invitations .nav-tab-wrapper,
     321body.tools_page_bp-optouts .nav-tab-wrapper {
     322        margin-bottom: 1em;
     323}
     324
    320325/*
    321326 * 2.4 Tooltips
    322327 */
  • src/bp-core/admin/css/common.css

    diff --git src/bp-core/admin/css/common.css src/bp-core/admin/css/common.css
    index 0272626bb..d2730f9a9 100644
    body.tools-php .bp-tools dd { 
    317317        margin: 0;
    318318}
    319319
     320body.tools_page_bp-members-invitations .nav-tab-wrapper,
     321body.tools_page_bp-optouts .nav-tab-wrapper {
     322        margin-bottom: 1em;
     323}
     324
    320325/*
    321326 * 2.4 Tooltips
    322327 */
  • src/bp-core/bp-core-filters.php

    diff --git src/bp-core/bp-core-filters.php src/bp-core/bp-core-filters.php
    index 735c7090a..85a8978d2 100644
    function bp_email_set_default_headers( $headers, $property, $transform, $email ) 
    10671067
    10681068        // Add 'List-Unsubscribe' header if applicable.
    10691069        if ( ! empty( $tokens['unsubscribe'] ) && $tokens['unsubscribe'] !== wp_login_url() ) {
    1070                 $user = get_user_by( 'email', $tokens['recipient.email'] );
     1070                $user    = get_user_by( 'email', $tokens['recipient.email'] );
     1071                $user_id = isset( $user->ID ) ? $user->ID : 0;
    10711072
    1072                 $link = bp_email_get_unsubscribe_link( array(
    1073                         'user_id'           => $user->ID,
     1073                $args = array(
     1074                        'user_id'           => $user_id,
    10741075                        'notification_type' => $email->get( 'type' ),
    1075                 ) );
     1076                );
     1077
     1078                // If this email is not to a current member, include the nonmember's email address and the Inviter ID.
     1079                if ( ! $user_id ) {
     1080                        $args['email_address'] = $tokens['recipient.email'];
     1081                        $args['member_id']     = bp_loggedin_user_id();
     1082                }
     1083
     1084                $link = bp_email_get_unsubscribe_link( $args );
    10761085
    10771086                if ( ! empty( $link ) ) {
    10781087                        $headers['List-Unsubscribe'] = sprintf( '<%s>', esc_url_raw( $link ) );
  • src/bp-core/bp-core-functions.php

    diff --git src/bp-core/bp-core-functions.php src/bp-core/bp-core-functions.php
    index 1535afed4..9464d61ad 100644
    function bp_core_add_page_mappings( $components, $existing = 'keep' ) { 
    702702
    703703        // Register and Activate are not components, but need pages when
    704704        // registration is enabled.
    705         if ( bp_get_signup_allowed() ) {
     705        if ( bp_get_signup_allowed() || bp_get_members_invitations_allowed()  ) {
    706706                foreach ( array( 'register', 'activate' ) as $slug ) {
    707707                        if ( ! isset( $pages[ $slug ] ) ) {
    708708                                $pages_to_create[ $slug ] = $page_titles[ $slug ];
    function bp_send_email( $email_type, $to, $args = array() ) { 
    34723472        // From, subject, content are set automatically.
    34733473        if ( 'settings-verify-email-change' === $email_type && isset( $args['tokens']['displayname'] ) ) {
    34743474                $email->set_to( $to, $args['tokens']['displayname'] );
     3475        // Emails sent to nonmembers will have no recipient.name populated.
     3476        } else if ( 'bp-members-invitation' === $email_type ) {
     3477                $email->set_to( $to, $to );
    34753478        } else {
    34763479                $email->set_to( $to );
    34773480        }
    function bp_email_get_schema() { 
    38153818                        /* translators: do not remove {} brackets or translate its contents. */
    38163819                        'post_title'   => __( '[{{{site.name}}}] You have an invitation to the group: "{{group.name}}"', 'buddypress' ),
    38173820                        /* translators: do not remove {} brackets or translate its contents. */
    3818                         'post_content' => __( "<a href=\"{{{inviter.url}}}\">{{inviter.name}}</a> has invited you to join the group: &quot;{{group.name}}&quot;.\n{{invite.message}}\n<a href=\"{{{invites.url}}}\">Go here to accept your invitation</a> or <a href=\"{{{group.url}}}\">visit the group</a> to learn more.", 'buddypress' ),
     3821                        'post_content' => __( "<a href=\"{{{inviter.url}}}\">{{inviter.name}}</a> has invited you to join the group: &quot;{{group.name}}&quot;.\n\n{{invite.message}}\n\n<a href=\"{{{invites.url}}}\">Go here to accept your invitation</a> or <a href=\"{{{group.url}}}\">visit the group</a> to learn more.", 'buddypress' ),
    38193822                        /* translators: do not remove {} brackets or translate its contents. */
    3820                         'post_excerpt' => __( "{{inviter.name}} has invited you to join the group: \"{{group.name}}\".\n\nTo accept your invitation, visit: {{{invites.url}}}\n\nTo learn more about the group, visit: {{{group.url}}}.\nTo view {{inviter.name}}'s profile, visit: {{{inviter.url}}}", 'buddypress' ),
     3823                        'post_excerpt' => __( "{{inviter.name}} has invited you to join the group: \"{{group.name}}\".\n\n{{invite.message}}\n\nTo accept your invitation, visit: {{{invites.url}}}\n\nTo learn more about the group, visit: {{{group.url}}}.\nTo view {{inviter.name}}'s profile, visit: {{{inviter.url}}}", 'buddypress' ),
    38213824                ),
    38223825                'groups-member-promoted' => array(
    38233826                        /* translators: do not remove {} brackets or translate its contents. */
    function bp_email_get_schema() { 
    38673870                        /* translators: do not remove {} brackets or translate its contents. */
    38683871                        'post_excerpt' => __( "Your membership request for the group \"{{group.name}}\" has been rejected.\n\nTo request membership again, visit: {{{group.url}}}", 'buddypress' ),
    38693872                ),
     3873                'bp-members-invitation' => array(
     3874                        /* translators: do not remove {} brackets or translate its contents. */
     3875                        'post_title'   => __( '{{inviter.name}} has invited you to join {{site.name}}', 'buddypress' ),
     3876                        /* translators: do not remove {} brackets or translate its contents. */
     3877                        'post_content' => __( "<a href=\"{{{inviter.url}}}\">{{inviter.name}}</a> has invited you to join the site: &quot;{{site.name}}&quot;.\n\n{{usermessage}}\n\n<a href=\"{{{invite.accept_url}}}\">Accept your invitation</a> or <a href=\"{{{site.url}}}\">visit the site</a> to learn more.", 'buddypress' ),
     3878                        /* translators: do not remove {} brackets or translate its contents. */
     3879                        'post_excerpt' => __( "{{inviter.name}} has invited you to join the site \"{{site.name}}\".\n\n{{usermessage}}\n\nTo accept your invitation, visit: {{{invite.accept_url}}}\n\nTo learn more about the site, visit: {{{site.url}}}.\nTo view {{inviter.name}}'s profile, visit: {{{inviter.url}}}", 'buddypress' ),
     3880                ),
    38703881        ) );
    38713882}
    38723883
    function bp_email_get_type_schema( $field = 'description' ) { 
    40124023                'description'   => __( 'Recipient has successfully activated an account.', 'buddypress' ),
    40134024        );
    40144025
     4026        $members_invitation = array(
     4027                'description'   => __( 'A site member has sent a site invitation to the recipient.', 'buddypress' ),
     4028                'unsubscribe'   => array(
     4029                        'meta_key'      => 'notification_bp_members_invite',
     4030                        'message'       => __( 'You will no longer receive emails when you are invited to join this site.', 'buddypress' ),
     4031                ),
     4032        );
     4033
    40154034        $types = array(
    40164035                'activity-comment'                   => $activity_comment,
    40174036                'activity-comment-author'            => $activity_comment_author,
    function bp_email_get_type_schema( $field = 'description' ) { 
    40304049                'groups-membership-request-accepted' => $groups_membership_request_accepted,
    40314050                'groups-membership-request-rejected' => $groups_membership_request_rejected,
    40324051                'core-user-activation'               => $core_user_activation,
     4052                'bp-members-invitation'              => $members_invitation,
    40334053        );
    40344054
    40354055        if ( $field !== 'all' ) {
    function bp_email_unsubscribe_handler() { 
    40494069        $raw_email_type = ! empty( $_GET['nt'] ) ? $_GET['nt'] : '';
    40504070        $raw_hash       = ! empty( $_GET['nh'] ) ? $_GET['nh'] : '';
    40514071        $raw_user_id    = ! empty( $_GET['uid'] ) ? absint( $_GET['uid'] ) : 0;
    4052         $new_hash       = hash_hmac( 'sha1', "{$raw_email_type}:{$raw_user_id}", bp_email_get_salt() );
     4072        $raw_user_email = ! empty( $_GET['uem'] ) ? $_GET['uem'] : '';
     4073        $raw_member_id  = ! empty( $_GET['mid'] ) ? absint( $_GET['mid'] ) : 0;
     4074        $redirect_to    = '';
     4075
     4076        $new_hash = '';
     4077        if ( ! empty( $raw_user_id ) ) {
     4078                $new_hash = hash_hmac( 'sha1', "{$raw_email_type}:{$raw_user_id}", bp_email_get_salt() );
     4079        } else if ( ! empty( $raw_user_email ) ) {
     4080                $new_hash = hash_hmac( 'sha1', "{$raw_email_type}:{$raw_user_email}", bp_email_get_salt() );
     4081        }
    40534082
    40544083        // Check required values.
    4055         if ( ! $raw_user_id || ! $raw_email_type || ! $raw_hash || ! array_key_exists( $raw_email_type, $emails ) ) {
     4084        if ( ( ! $raw_user_id && ! $raw_user_email ) || ! $raw_email_type || ! $raw_hash || ! array_key_exists( $raw_email_type, $emails ) ) {
    40564085                $redirect_to = wp_login_url();
    40574086                $result_msg  = __( 'Something has gone wrong.', 'buddypress' );
    40584087                $unsub_msg   = __( 'Please log in and go to your settings to unsubscribe from notification emails.', 'buddypress' );
    function bp_email_unsubscribe_handler() { 
    40784107                        $redirect_to = bp_core_get_user_domain( get_current_user_id() );
    40794108                }
    40804109
     4110        // This is an unsubscribe request from a nonmember.
     4111        } else if ( $raw_user_email ) {
     4112                // Unsubscribe.
     4113                if ( bp_user_has_opted_out() ) {
     4114                        $result_msg = $emails[ $raw_email_type ]['unsubscribe']['message'];
     4115                        $unsub_msg  = __( 'You have already unsubscribed from all communication from this site.', 'buddypress' );
     4116                } else {
     4117                        $optout_args = array(
     4118                                'email_address' => $raw_user_email,
     4119                                'user_id'       => $raw_member_id,
     4120                                'email_type'    => $raw_email_type,
     4121                                'date_modified' => bp_core_current_time(),
     4122                        );
     4123                        bp_add_optout( $optout_args );
     4124                        $result_msg = $emails[ $raw_email_type ]['unsubscribe']['message'];
     4125                        $unsub_msg  = __( 'You have been unsubscribed.', 'buddypress' );
     4126                }
     4127
     4128        // This is an unsubscribe request from a current member.
    40814129        } else {
    40824130                if ( bp_is_active( 'settings' ) ) {
    40834131                        $redirect_to = sprintf(
    function bp_email_unsubscribe_handler() { 
    40974145                $unsub_msg  = __( 'You can change this or any other email notification preferences in your email settings.', 'buddypress' );
    40984146        }
    40994147
    4100         $message = sprintf(
    4101                 '%1$s <a href="%2$s">%3$s</a>',
    4102                 $result_msg,
    4103                 esc_url( $redirect_to ),
    4104                 esc_html( $unsub_msg )
    4105         );
     4148        if ( $raw_user_id && $redirect_to ) {
     4149                $message = sprintf(
     4150                        '%1$s <a href="%2$s">%3$s</a>',
     4151                        $result_msg,
     4152                        esc_url( $redirect_to ),
     4153                        esc_html( $unsub_msg )
     4154                );
    41064155
    4107         bp_core_add_message( $message );
    4108         bp_core_redirect( bp_core_get_user_domain( $raw_user_id ) );
     4156                // Template notices are only displayed on BP pages.
     4157                bp_core_add_message( $message );
     4158                bp_core_redirect( bp_core_get_user_domain( $raw_user_id ) );
    41094159
    4110         exit;
     4160                exit;
     4161        } else {
     4162                wp_die(
     4163                        sprintf( '%1$s %2$s', esc_html( $unsub_msg ), esc_html( $result_msg ) ),
     4164                        esc_html( $unsub_msg ),
     4165                        array(
     4166                                'link_url'  => home_url(),
     4167                                'link_text' => __( 'Go to website\'s home page.', 'buddypress' ),
     4168                        )
     4169                );
     4170        }
    41114171}
    41124172
    41134173/**
    function bp_email_unsubscribe_handler() { 
    41224182 *    @type string $notification_type Which notification type is being sent.
    41234183 *    @type string $user_id           The ID of the user to whom the notification is sent.
    41244184 *    @type string $redirect_to       Optional. The url to which the user will be redirected. Default is the activity directory.
     4185 *    @type string $email             Optional. The email address of the user to whom the notification is sent.
    41254186 * }
    41264187 * @return string The unsubscribe link.
    41274188 */
    function bp_email_get_unsubscribe_link( $args ) { 
    41414202                return '';
    41424203        }
    41434204
    4144         $link = add_query_arg(
    4145                 array(
    4146                         'action' => 'unsubscribe',
    4147                         'nh'     => hash_hmac( 'sha1', "{$email_type}:{$user_id}", bp_email_get_salt() ),
    4148                         'nt'     => $args['notification_type'],
    4149                         'uid'    => $user_id,
    4150                 ),
    4151                 $redirect_to
    4152         );
     4205        $link = '';
     4206        // Case where the recipient is a member of the site.
     4207        if ( ! empty( $user_id ) ) {
     4208                $link = add_query_arg(
     4209                        array(
     4210                                'action' => 'unsubscribe',
     4211                                'nh'     => hash_hmac( 'sha1', "{$email_type}:{$user_id}", bp_email_get_salt() ),
     4212                                'nt'     => $args['notification_type'],
     4213                                'uid'    => $user_id,
     4214                        ),
     4215                        $redirect_to
     4216                );
     4217
     4218        // Case where the recipient is not a member of the site.
     4219        } else if ( ! empty( $args['email_address'] ) ) {
     4220                $email_address = $args['email_address'];
     4221                $member_id     = (int) $args['member_id'];
     4222                $link          = add_query_arg(
     4223                        array(
     4224                                'action' => 'unsubscribe',
     4225                                'nh'     => hash_hmac( 'sha1', "{$email_type}:{$email_address}", bp_email_get_salt() ),
     4226                                'nt'     => $args['notification_type'],
     4227                                'mid'    => $member_id,
     4228                                'uem'    => $email_address,
     4229                        ),
     4230                        $redirect_to
     4231                );
     4232        }
    41534233
    41544234        /**
    41554235         * Filters the unsubscribe link.
    function bp_get_optouts( $args = array() ) { 
    43604440        return $optout_class::get( $args );
    43614441}
    43624442
     4443/**
     4444 * Check an email address to see if that individual has opted out.
     4445 *
     4446 * @since 8.0.0
     4447 *
     4448 * @param string $email_address Email address to check.
     4449 * @return bool True if the user has opted out, false otherwise.
     4450 */
     4451function bp_user_has_opted_out( $email_address = '' ) {
     4452        $optout_class = new BP_Optout();
     4453        $optout_id    = $optout_class->optout_exists(
     4454                array(
     4455                        'email_address' => $email_address,
     4456                )
     4457        );
     4458        return (bool) $optout_id;
     4459}
     4460
    43634461/**
    43644462 * Delete a BP_Optout by ID.
    43654463 *
  • src/bp-core/bp-core-template.php

    diff --git src/bp-core/bp-core-template.php src/bp-core/bp-core-template.php
    index 799a69cdc..e840a7cd6 100644
    function bp_is_settings_component() { 
    22472247        return (bool) bp_is_current_component( 'settings' );
    22482248}
    22492249
     2250/**
     2251 * Check whether the current page is an Invitations screen.
     2252 *
     2253 * @since 8.0.0
     2254 *
     2255 * @return bool True if the current page is an Invitations screen.
     2256 */
     2257function bp_is_members_invitations_screen() {
     2258        return (bool) bp_is_current_component( bp_get_members_invitations_slug() );
     2259}
     2260
    22502261/**
    22512262 * Is the current component an active core component?
    22522263 *
    function bp_is_user_settings_profile() { 
    26472658        return (bool) ( bp_is_user_settings() && bp_is_current_action( 'profile' ) );
    26482659}
    26492660
     2661/**
     2662 * Is the current page a user's community invitations page?
     2663 *
     2664 * Eg http://example.com/members/cassie/invitations/ (or a subpage thereof).
     2665 *
     2666 * @since 8.0.0
     2667 *
     2668 * @return bool True if the current page is a user's community invitations page.
     2669 */
     2670function bp_is_user_members_invitations() {
     2671        return (bool) ( bp_is_user() && bp_is_members_invitations_screen() );
     2672}
     2673
     2674/**
     2675 * Is the current page a user's List Invites page?
     2676 *
     2677 * Eg http://example.com/members/cassie/invitations/list-invites/.
     2678 *
     2679 * @since 8.0.0
     2680 *
     2681 * @return bool True if the current page is a user's List Invites page.
     2682 */
     2683function bp_is_user_members_invitations_list() {
     2684        return (bool) ( bp_is_user_members_invitations() && bp_is_current_action( 'list-invites' ) );
     2685}
     2686
     2687/**
     2688 * Is the current page a user's Send Invites page?
     2689 *
     2690 * Eg http://example.com/members/cassie/invitations/send-invites/.
     2691 *
     2692 * @since 8.0.0
     2693 *
     2694 * @return bool True if the current page is a user's Send Invites page.
     2695 */
     2696function bp_is_user_members_invitations_send_screen() {
     2697        return (bool) ( bp_is_user_members_invitations() && bp_is_current_action( 'send-invites' ) );
     2698}
     2699
    26502700/** Groups ********************************************************************/
    26512701
    26522702/**
  • src/bp-core/bp-core-update.php

    diff --git src/bp-core/bp-core-update.php src/bp-core/bp-core-update.php
    index 6747c3ac5..03710213c 100644
    function bp_core_get_8_0_upgrade_email_schema( $emails ) { 
    697697                $new_emails['core-user-activation'] = $emails['core-user-activation'];
    698698        }
    699699
     700        if ( isset( $emails['bp-members-invitation'] ) ) {
     701                $new_emails['bp-members-invitation'] = $emails['bp-members-invitation'];
     702        }
     703
    700704        return $new_emails;
    701705}
    702706
  • 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 1e10deab7..bbf945471 100644
    class BP_Admin { 
    411411                        register_setting( 'buddypress', 'bp-disable-cover-image-uploads', 'intval' );
    412412                }
    413413
     414                // Community Invitations.
     415                if ( bp_is_active( 'members', 'invitations' ) ) {
     416                        add_settings_field( 'bp-enable-members-invitations', __( 'Invitations', 'buddypress' ), 'bp_admin_setting_callback_members_invitations', 'buddypress', 'bp_members' );
     417                        register_setting( 'buddypress', 'bp-enable-members-invitations', 'intval' );
     418                }
     419
    414420                /* XProfile Section **************************************************/
    415421
    416422                if ( bp_is_active( 'xprofile' ) ) {
  • src/bp-core/classes/class-bp-email.php

    diff --git src/bp-core/classes/class-bp-email.php src/bp-core/classes/class-bp-email.php
    index 78188fe4e..c2a5865fc 100644
    class BP_Email { 
    999999                        $retval = new WP_Error( 'missing_parameter', __CLASS__, $this );
    10001000                }
    10011001
     1002                // Has the user opted out of receiving any email from this site?
     1003                $recipient_to       = $this->get_to();
     1004                $recipient_to_first = array_shift( $recipient_to );
     1005                $recipient_address  = $recipient_to_first->get_address();
     1006                if ( bp_user_has_opted_out( $recipient_address ) ) {
     1007                        $retval = new WP_Error( 'user_has_opted_out', __( 'The email recipient has opted out from receiving communication from this site.', 'buddypress' ), $recipient_address );
     1008                }
     1009
    10021010                /**
    10031011                 * Filters whether the email passes basic validation checks.
    10041012                 *
  • src/bp-core/classes/class-bp-invitation-manager.php

    diff --git src/bp-core/classes/class-bp-invitation-manager.php src/bp-core/classes/class-bp-invitation-manager.php
    index 0b7d2f009..a478c2c81 100644
    abstract class BP_Invitation_Manager { 
    9999                        return false;
    100100                }
    101101
     102                // If an email address is specified, it must be a valid email address.
     103                if ( $r['invitee_email'] && ! is_email( $r['invitee_email'] ) ) {
     104                        return false;
     105                }
     106
    102107                /**
    103108                 * Is this user allowed to extend invitations in this situation?
    104109                 *
    abstract class BP_Invitation_Manager { 
    157162         * @param int   $invitation_id ID of invitation to send.
    158163         * @param array $args          See BP_Invitation::mark_sent().
    159164         *
    160          * @return int|bool The number of rows updated, or false on error.
     165         * @return bool The result of `run_send_action()`.
    161166         */
    162167        public function send_invitation_by_id( $invitation_id = 0, $args = array() ) {
    163168                $updated = false;
    abstract class BP_Invitation_Manager { 
    195200                }
    196201
    197202                // Perform the send action.
    198                 $this->run_send_action( $invitation );
     203                $success = $this->run_send_action( $invitation );
    199204
    200                 $updated = BP_Invitation::mark_sent( $invitation->id, $args );
     205                if ( $success ) {
     206                        BP_Invitation::mark_sent( $invitation->id, $args );
     207                }
    201208
    202                 return $updated;
     209                return $success;
    203210        }
    204211
    205212        /**
    abstract class BP_Invitation_Manager { 
    309316         * @param int   $request_id ID of request to send.
    310317         * @param array $args       See BP_Invitation::mark_sent().
    311318         *
    312          * @return int|bool The number of rows updated, or false on error.
     319         * @return bool The result of `run_send_action()`.
    313320         */
    314321        public function send_request_notification_by_id( $request_id = 0, $args = array() ) {
    315322                $updated = false;
    abstract class BP_Invitation_Manager { 
    342349                }
    343350
    344351                // Perform the send action.
    345                 $this->run_send_action( $request );
     352                $success = $this->run_send_action( $request );
    346353
    347                 $updated = BP_Invitation::mark_sent( $request->id, $args );
     354                if ( $success ) {
     355                        BP_Invitation::mark_sent( $request->id, $args );
     356                }
    348357
    349                 return $updated;
     358                return $success;
    350359        }
    351360
    352361        /** Retrieve ******************************************************************/
    abstract class BP_Invitation_Manager { 
    383392                return BP_Invitation::get( $args );
    384393        }
    385394
     395        /**
     396         * Get a count of the number of invitations that match provided filter parameters.
     397         *
     398         * @since 8.0.0
     399         *
     400         * @see BP_Invitation::get_total_count() for a description of accepted parameters.
     401         *
     402         * @return int Total number of invitations.
     403         */
     404        public function get_invitations_total_count( $args = array() ) {
     405                // Default to returning invitations, not requests.
     406                if ( empty( $args['type'] ) ) {
     407                        $args['type'] = 'invite';
     408                }
     409                // Use the class_name property value.
     410                $args['class'] = $this->class_name;
     411
     412                return BP_Invitation::get_total_count( $args );
     413        }
     414
    386415        /**
    387416         * Get requests, based on provided filter parameters.
    388417         *
    abstract class BP_Invitation_Manager { 
    475504         public function accept_invitation( $args = array() ) {
    476505
    477506                $r = bp_parse_args( $args, array(
     507                        'id'                => false,
    478508                        'user_id'           => 0,
    479509                        'invitee_email'     => '',
    480510                        'item_id'           => null,
    abstract class BP_Invitation_Manager { 
    484514                ), 'accept_invitation' );
    485515                $r['class'] = $this->class_name;
    486516
    487                 if ( ! ( ( $r['user_id'] || $r['invitee_email'] ) && $r['class'] && $r['item_id'] ) ) {
     517                if ( ! $r['id'] && ! ( ( $r['user_id'] || $r['invitee_email'] ) && $r['class'] && $r['item_id'] ) ) {
    488518                        return false;
    489519                }
    490520
    abstract class BP_Invitation_Manager { 
    703733                ) );
    704734        }
    705735
     736        /**
     737         * Delete an invitation by id.
     738         *
     739         * @since 8.0.0
     740         *
     741         * @param int $id ID of the invitation to delete.
     742         * @return int|bool Number of rows deleted on success, false on failure.
     743         */
     744        public function delete_by_id( $id ) {
     745                // Ensure that the invitation exists and was created by this class.
     746                $invite = new BP_Invitation( $id );
     747                if ( ! $invite->id || sanitize_key( $this->class_name ) !== $invite->class ) {
     748                        return false;
     749                }
     750
     751                return BP_Invitation::delete_by_id( $id );
     752        }
     753
     754
     755
    706756        /**
    707757         * This is where custom actions are added (in child classes)
    708758         * to determine whether an invitation should be allowed.
  • src/bp-core/classes/class-bp-invitation.php

    diff --git src/bp-core/classes/class-bp-invitation.php src/bp-core/classes/class-bp-invitation.php
    index 2f5504df2..c1e941de2 100644
    class BP_Invitation { 
    660660         *     Associative array of arguments. All arguments but $page and
    661661         *     $per_page can be treated as filter values for get_where_sql()
    662662         *     and get_query_clauses(). All items are optional.
    663          *     @type int|array    $id                ID of invitation being updated.
     663         *     @type int|array    $id                ID of invitation being fetched.
    664664         *                                           Can be an array of IDs.
    665665         *     @type int|array    $user_id           ID of user being queried. Can be an
    666666         *                                           Can be an array of IDs.
  • src/bp-members/admin/bp-members-admin-classes.php

    diff --git src/bp-members/admin/bp-members-admin-classes.php src/bp-members/admin/bp-members-admin-classes.php
    index 96e339e7a..4e2e79b54 100644
    defined( 'ABSPATH' ) || exit; 
    1212
    1313if ( class_exists( 'WP_Users_List_Table' ) ) {
    1414        require dirname( dirname( __FILE__ ) ) . '/classes/class-bp-members-list-table.php';
     15        require dirname( dirname( __FILE__ ) ) . '/classes/class-bp-members-invitations-list-table.php';
    1516}
    1617
    1718if ( class_exists( 'WP_MS_Users_List_Table' ) ) {
  • src/bp-members/bp-members-activity.php

    diff --git src/bp-members/bp-members-activity.php src/bp-members/bp-members-activity.php
    index 8907d0a7e..a110cd6e7 100644
    add_action( 'bp_register_activity_actions', 'bp_members_register_activity_action 
    5656 * @return string $action
    5757 */
    5858function bp_members_format_activity_action_new_member( $action, $activity ) {
    59         $userlink = bp_core_get_userlink( $activity->user_id );
     59        $userlink         = bp_core_get_userlink( $activity->user_id );
     60        $inviter_userlink = false;
     61        $invite_id        = bp_get_user_meta( $activity->user_id, 'accepted_members_invitation', true );
    6062
    61         /* translators: %s: user link */
    62         $action = sprintf( esc_html__( '%s became a registered member', 'buddypress' ), $userlink );
     63        if ( $invite_id ) {
     64                $invite = new BP_Invitation( (int) $invite_id );
     65
     66                if ( $invite->inviter_id ) {
     67                        $inviter_userlink = bp_core_get_userlink( $invite->inviter_id );
     68                }
     69        }
     70
     71        if ( $inviter_userlink ) {
     72                $action = sprintf(
     73                        /* translators: 1: new user link. 2: inviter user link. */
     74                        esc_html__( '%1$s accepted an invitation from %2$s and became a registered member', 'buddypress' ),
     75                        $userlink,
     76                        $inviter_userlink
     77                );
     78        } else {
     79                /* translators: %s: user link */
     80                $action = sprintf( esc_html__( '%s became a registered member', 'buddypress' ), $userlink );
     81        }
    6382
    6483        // Legacy filter - pass $user_id instead of $activity.
    6584        if ( has_filter( 'bp_core_activity_registered_member_action' ) ) {
    function bp_members_format_activity_action_new_member( $action, $activity ) { 
    6988        /**
    7089         * Filters the formatted 'new member' activity actions.
    7190         *
    72          * @since 2.2.0
     91         * @since 8.0.0
    7392         *
    74          * @param string $action   Static activity action.
    75          * @param object $activity Activity object.
     93         * @param string $action    Static activity action.
     94         * @param object $activity  Activity object.
     95         * @param int    $invite_id The ID of the invite.
    7696         */
    77         return apply_filters( 'bp_members_format_activity_action_new_member', $action, $activity );
     97        return apply_filters( 'bp_members_format_activity_action_new_member', $action, $activity, $invite_id );
    7898}
    7999
    80100/**
  • src/bp-members/bp-members-adminbar.php

    diff --git src/bp-members/bp-members-adminbar.php src/bp-members/bp-members-adminbar.php
    index 10f784d09..282be654c 100644
    function bp_members_admin_bar_my_account_menu() { 
    2323        global $wp_admin_bar;
    2424
    2525        // Bail if this is an ajax request.
    26         if ( defined( 'DOING_AJAX' ) )
     26        if ( wp_doing_ajax() ) {
    2727                return;
     28        }
    2829
    2930        // Logged in user.
    3031        if ( is_user_logged_in() ) {
    function bp_members_remove_edit_page_menu() { 
    178179        }
    179180}
    180181add_action( 'add_admin_bar_menus', 'bp_members_remove_edit_page_menu' );
     182
     183/**
     184 * Add the "Invitations" menu and submenus.
     185 *
     186 * @since 8.0.0
     187 */
     188function bp_members_admin_bar_add_invitations_menu() {
     189        global $wp_admin_bar;
     190
     191        // Bail if this is an ajax request.
     192        if ( wp_doing_ajax() ) {
     193                return;
     194        }
     195
     196        if ( is_user_logged_in() && bp_get_members_invitations_allowed() && ( bp_current_user_can( 'bp_members_send_invitation' ) || bp_members_invitations_user_has_sent_invites() ) ) {
     197                $bp               = buddypress();
     198                $invitations_link = trailingslashit( bp_loggedin_user_domain() . bp_get_members_invitations_slug() );
     199
     200                $wp_admin_bar->add_node(
     201                        array(
     202                                'id'     => $bp->my_account_menu_id . '-invitations',
     203                                'parent' => $bp->my_account_menu_id,
     204                                'title'  => __( 'Invitations', 'buddypress' ),
     205                                'href'   => $invitations_link,
     206                                'meta'   => array(
     207                                        'class'  => 'ab-sub-secondary'
     208                                )
     209                        )
     210                );
     211
     212                if ( bp_current_user_can( 'bp_members_send_invitation' ) ) {
     213                        $wp_admin_bar->add_node(
     214                                array(
     215                                        'id'     => $bp->my_account_menu_id . '-invitations-send',
     216                                        'parent' => $bp->my_account_menu_id . '-invitations',
     217                                        'title'  => __( 'Send Invites', 'buddypress' ),
     218                                        'href'   => $invitations_link,
     219                                        'meta'   => array(
     220                                                'class'  => 'ab-sub-secondary'
     221                                        )
     222                                )
     223                        );
     224                }
     225
     226                $wp_admin_bar->add_node(
     227                        array(
     228                                'id'     => $bp->my_account_menu_id . '-invitations-list',
     229                                'parent' => $bp->my_account_menu_id . '-invitations',
     230                                'title'  => __( 'Pending Invites', 'buddypress' ),
     231                                'href'   => $invitations_link . 'list-invites/',
     232                                'meta'   => array(
     233                                        'class'  => 'ab-sub-secondary'
     234                                )
     235                        )
     236                );
     237        }
     238}
     239add_action( 'bp_setup_admin_bar', 'bp_members_admin_bar_add_invitations_menu', 90 );
  • src/bp-members/bp-members-filters.php

    diff --git src/bp-members/bp-members-filters.php src/bp-members/bp-members-filters.php
    index 5575ebef2..4c6eacead 100644
    function bp_members_edit_profile_url( $url, $user_id, $scheme = 'admin' ) { 
    127127        return apply_filters( 'bp_members_edit_profile_url', $profile_link, $url, $user_id, $scheme );
    128128}
    129129add_filter( 'edit_profile_url', 'bp_members_edit_profile_url', 10, 3 );
     130
     131/**
     132 * Filter the bp_user_can value to determine what the user can do in the members component.
     133 *
     134 * @since 8.0.0
     135 *
     136 * @param bool   $retval     Whether or not the current user has the capability.
     137 * @param int    $user_id
     138 * @param string $capability The capability being checked for.
     139 * @param int    $site_id    Site ID. Defaults to the BP root blog.
     140 * @param array  $args       Array of extra arguments passed.
     141 *
     142 * @return bool
     143 */
     144function bp_members_user_can_filter( $retval, $user_id, $capability, $site_id, $args = array() ) {
     145
     146        switch ( $capability ) {
     147                case 'bp_members_manage_membership_requests':
     148                        $retval = bp_user_can( $user_id, 'bp_moderate' );
     149                        break;
     150
     151                case 'bp_members_send_invitation':
     152                        if ( bp_get_members_invitations_allowed() ) {
     153                                $retval = true;
     154                        }
     155                        break;
     156
     157                case 'bp_members_receive_invitation':
     158                        if ( bp_get_members_invitations_allowed() ) {
     159                                $retval = true;
     160                                // The invited user must not already be a member of the network.
     161                                if ( empty( $args['invitee_email'] ) || false !== get_user_by( 'email', $args['invitee_email'] ) ) {
     162                                        $retval = false;
     163                                }
     164                                // The invited user must not have opted out from being contacted from this site.
     165                                if ( bp_user_has_opted_out( $args['invitee_email'] ) ) {
     166                                        $retval = false;
     167                                }
     168                        }
     169                        break;
     170        }
     171
     172        return $retval;
     173}
     174add_filter( 'bp_user_can', 'bp_members_user_can_filter', 10, 5 );
     175
     176/**
     177 * Do not allow the new user to change the email address
     178 * if they are accepting a community invitation.
     179 *
     180 * @since 8.0.0
     181 *
     182 * @param array  $attributes The field attributes.
     183 * @param string $name       The field name.
     184 *
     185 * @return array $attributes The field attributes.
     186 */
     187function bp_members_invitations_make_registration_email_input_readonly_if_invite( $attributes, $name ) {
     188        if ( 'email' === $name && bp_get_members_invitations_allowed() ) {
     189                $invite = bp_get_members_invitation_from_request();
     190                if ( $invite->id ) {
     191                        $attributes['readonly'] = 'readonly';
     192                }
     193        }
     194        return $attributes;
     195}
     196add_filter( 'bp_get_form_field_attributes', 'bp_members_invitations_make_registration_email_input_readonly_if_invite', 10, 2 );
     197
     198/**
     199 * Provide a more-specific welcome message if the new user
     200 * is accepting a network invitation.
     201 *
     202 * @since 8.0.0
     203 *
     204 * @return string $message The message text.
     205 */
     206function bp_members_invitations_get_registration_welcome_message() {
     207        $message = '';
     208        if ( ! bp_get_members_invitations_allowed() ) {
     209                return $message;
     210        }
     211        $invite = bp_get_members_invitation_from_request();
     212        if ( ! $invite->id || ! $invite->invitee_email ) {
     213                return $message;
     214        }
     215
     216        // Check if the user is already a site member.
     217        $maybe_user = get_user_by( 'email', $invite->invitee_email );
     218
     219        // This user is already a member
     220        if ( $maybe_user ) {
     221                $message = sprintf( __( 'Welcome! You are already a member of this site. Please <a href="%1s">log in</a> to continue. If you have forgotten your password, you can <a href="%2s">reset it</a>.', 'buddypress' ), wp_login_url( bp_get_root_domain() ), wp_lostpassword_url(  bp_get_root_domain() ) );
     222        // This user can register!
     223        } else {
     224
     225                // Fetch the display names of all inviters to personalize the welcome message.
     226                $args = array(
     227                        'invitee_email' => $invite->invitee_email,
     228                        'invite_sent'   => 'sent',
     229                );
     230                $all_invites = bp_members_invitations_get_invites( $all_args );
     231                $inviters = array();
     232                foreach ( $all_invites as $inv ) {
     233                        $inviters[] = bp_core_get_user_displayname( $inv->inviter_id );
     234                }
     235
     236                if ( ! empty( $inviters ) ) {
     237                        $message = sprintf( _n( 'Welcome! You&#8217;ve been invited to join the site by the following user: %s. ', 'Welcome! You&#8217;ve been invited to join the site by the following users: %s. ', count( $inviters ), 'buddypress' ), implode( ', ', $inviters ) );
     238                } else {
     239                        $message = __( 'Welcome! You&#8217;ve been invited to join the site. ', 'buddypress' );
     240                }
     241        }
     242
     243        return $message;
     244}
     245
     246/**
     247 * Provide a more-specific "registration is disabled" message
     248 * if registration is available by invitation only.
     249 * Also provide failure note if new user is trying to accept
     250 * a network invitation but there's a problem.
     251 *
     252 * @since 8.0.0
     253 *
     254 * @return string $message The message text.
     255 */
     256function bp_members_invitations_get_modified_registration_disabled_message() {
     257        $message = '';
     258        if ( bp_get_members_invitations_allowed() ) {
     259
     260                $invite = bp_get_members_invitation_from_request();
     261                if ( ! $invite->id || ! $invite->invitee_email ) {
     262                        return $message;
     263                }
     264
     265                // Check if the user is already a site member.
     266                $maybe_user = get_user_by( 'email', $invite->invitee_email );
     267
     268                if ( ! $maybe_user ) {
     269                        $message = __( 'Member registration is allowed by invitation only.', 'buddypress' );
     270                        // Is the user trying to accept an invitation but something is wrong?
     271                        if ( ! empty( $_GET['inv'] ) ) {
     272                                $message .= __( ' It looks like there is a problem with your invitation. Please try again.', 'buddypress' );
     273                        }
     274                } else if ( 'nouveau' === bp_get_theme_package_id() ) {
     275                        $message = sprintf( __( 'Welcome! You are already a member of this site. Please <a href="%1s">log in</a> to continue. If you have forgotten your password, you can <a href="%2s">reset it</a>.', 'buddypress' ), wp_login_url( bp_get_root_domain() ), wp_lostpassword_url(  bp_get_root_domain() ) );
     276                }
     277        }
     278        return $message;
     279}
     280
     281/**
     282 * Sanitize the invitation property output.
     283 *
     284 * @since 8.0.0
     285 *
     286 * @param int|string $value    The value for the requested property.
     287 * @param string     $property The name of the requested property.
     288 * @param string     $context  The context of display.
     289 * @return int|string          The sanitized value.
     290 */
     291function bp_members_sanitize_invitation_property( $value = '', $property = '', $context = 'html' ) {
     292        if ( ! $property ) {
     293                return '';
     294        }
     295
     296        switch ( $property ) {
     297                case 'id':
     298                case 'user_id':
     299                case 'item_id':
     300                case 'secondary_item_id':
     301                        $value = absint( $value );
     302                        break;
     303                case 'invite_sent':
     304                case 'accepted':
     305                        $value = absint( $value ) ? __( 'Yes', 'buddypress' ) : __( 'No', 'buddypress' );
     306                        $value = 'attribute' === $context ? esc_attr( $value ) : esc_html( $value );
     307                        break;
     308                case 'invitee_email':
     309                        $value = sanitize_email( $value );
     310                        break;
     311                case 'content':
     312                        $value = wp_kses( $value, array() );
     313                        $value = wptexturize( $value );
     314                        break;
     315                case 'date_modified':
     316                        $value = mysql2date( 'Y/m/d g:i:s a', $value );
     317                        $value = 'attribute' === $context ? esc_attr( $value ) : esc_html( $value );
     318                        break;
     319
     320                default:
     321                        $value = 'attribute' === $context ? esc_attr( $value ) : esc_html( $value );
     322                        break;
     323        }
     324
     325        return $value;
     326}
     327add_filter( 'bp_the_members_invitation_property', 'bp_members_sanitize_invitation_property', 10, 3 );
  • src/bp-members/bp-members-functions.php

    diff --git src/bp-members/bp-members-functions.php src/bp-members/bp-members-functions.php
    index 347614fc8..b6fdc0122 100644
    function bp_send_welcome_email( $user_id = 0 ) { 
    33423342        bp_send_email( 'core-user-activation', $user_id, array( 'tokens' => $welcome_tokens ) );
    33433343}
    33443344add_action( 'bp_core_activated_user', 'bp_send_welcome_email', 10, 1 );
     3345
     3346/**
     3347 * Get invitations to the BP community filtered by arguments.
     3348 *
     3349 * @since 8.0.0
     3350 *
     3351 * @param array $args     Invitation arguments.
     3352 *                        See BP_Invitation::get() for list.
     3353 *
     3354 * @return array $invites     Matching BP_Invitation objects.
     3355 */
     3356function bp_members_invitations_get_invites( $args = array() ) {
     3357        $invites_class = new BP_Members_Invitation_Manager();
     3358        return $invites_class->get_invitations( $args );
     3359}
     3360
     3361/**
     3362 * Check whether a user has sent any community invitations.
     3363 *
     3364 * @since 8.0.0
     3365 *
     3366 * @param int $user_id ID of user to check for invitations sent by.
     3367 *                     Defaults to the current user's ID.
     3368 *
     3369 * @return bool $invites True if user has sent invites.
     3370 */
     3371function bp_members_invitations_user_has_sent_invites( $user_id = 0 ) {
     3372        if ( 0 === $user_id ) {
     3373                $user_id = bp_loggedin_user_id();
     3374                if ( ! $user_id ) {
     3375                        return false;
     3376                }
     3377        }
     3378        $invites_class = new BP_Members_Invitation_Manager();
     3379        $args = array(
     3380                'inviter_id' => $user_id,
     3381        );
     3382        return (bool) $invites_class->invitation_exists( $args );
     3383}
     3384
     3385/**
     3386 * Invite a user to a BP community.
     3387 *
     3388 * @since 8.0.0
     3389 *
     3390 * @param array|string $args {
     3391 *     Array of arguments.
     3392 *     @type int    $invitee_email Email address of the user being invited.
     3393 *     @type int    $network_id    ID of the network to which the user is being invited.
     3394 *     @type int    $inviter_id    Optional. ID of the inviting user. Default:
     3395 *                                 ID of the logged-in user.
     3396 *     @type string $date_modified Optional. Modified date for the invitation.
     3397 *                                 Default: current date/time.
     3398 *     @type string $content       Optional. Message to invitee.
     3399 *     @type bool   $send_invite   Optional. Whether the invitation should be
     3400 *                                 sent now. Default: false.
     3401 * }
     3402 * @return bool True on success, false on failure.
     3403 */
     3404function bp_members_invitations_invite_user( $args ) {
     3405        $r = bp_parse_args( $args, array(
     3406                'invitee_email' => '',
     3407                'network_id'    => get_current_network_id(),
     3408                'inviter_id'    => bp_loggedin_user_id(),
     3409                'date_modified' => bp_core_current_time(),
     3410                'content'       => '',
     3411                'send_invite'   => 0
     3412        ), 'community_invite_user' );
     3413
     3414        $inv_args = array(
     3415                'invitee_email' => $r['invitee_email'],
     3416                'item_id'       => $r['network_id'],
     3417                'inviter_id'    => $r['inviter_id'],
     3418                'date_modified' => $r['date_modified'],
     3419                'content'       => $r['content'],
     3420                'send_invite'   => $r['send_invite']
     3421        );
     3422
     3423        // Create the invitataion.
     3424        $invites_class = new BP_Members_Invitation_Manager();
     3425        $created       = $invites_class->add_invitation( $inv_args );
     3426
     3427        /**
     3428         * Fires after the creation of a new network invite.
     3429         *
     3430         * @since 8.0.0
     3431         *
     3432         * @param array    $r       Array of parsed arguments for the network invite.
     3433         * @param int|bool $created The ID of the invitation or false if it couldn't be created.
     3434         */
     3435        do_action( 'bp_members_invitations_invite_user', $r, $created );
     3436
     3437        return $created;
     3438}
     3439
     3440/**
     3441 * Resend a membership invitation email by id.
     3442 *
     3443 * @since 8.0.0
     3444 *
     3445 * @param int $id ID of the invitation to resend.
     3446 * @return bool True on success, false on failure.
     3447 */
     3448function bp_members_invitation_resend_by_id( $id = 0 ) {
     3449
     3450        // Find the invitation before deleting it.
     3451        $existing_invite = new BP_Invitation( $id );
     3452        $invites_class   = new BP_Members_Invitation_Manager();
     3453        $success         = $invites_class->send_invitation_by_id( $id );
     3454
     3455        if ( ! $success ) {
     3456                return $success;
     3457        }
     3458
     3459        /**
     3460         * Fires after the re-sending of a network invite.
     3461         *
     3462         * @since 8.0.0
     3463         *
     3464         * @param BP_Invitation $existing_invite The invitation that was resent.
     3465         */
     3466        do_action( 'bp_members_invitations_resend_invitation', $existing_invite );
     3467
     3468        return $success;
     3469}
     3470
     3471/**
     3472 * Delete a membership invitation by id.
     3473 *
     3474 * @since 8.0.0
     3475 *
     3476 * @param int $id ID of the invitation to delete.
     3477 * @return int|bool Number of rows deleted on success, false on failure.
     3478 */
     3479function bp_members_invitations_delete_by_id( $id = 0 ) {
     3480
     3481        // Find the invitation before deleting it.
     3482        $existing_invite = new BP_Invitation( $id );
     3483        $invites_class   = new BP_Members_Invitation_Manager();
     3484        $success         = $invites_class->delete_by_id( $id );
     3485
     3486        if ( ! $success ) {
     3487                return $success;
     3488        }
     3489
     3490        // Run a different action depending on the status of the invite.
     3491        if ( ! $existing_invite->invite_sent ) {
     3492                /**
     3493                 * Fires after the deletion of an unsent community invite.
     3494                 *
     3495                 * @since 8.0.0
     3496                 *
     3497                 * @param BP_Invitation $existing_invite The invitation to be deleted.
     3498                 */
     3499                do_action( 'bp_members_invitations_canceled_invitation', $existing_invite );
     3500        } else if ( ! $existing_invite->accepted ) {
     3501                /**
     3502                 * Fires after the deletion of a sent, but not yet accepted, community invite.
     3503                 *
     3504                 * @since 8.0.0
     3505                 *
     3506                 * @param BP_Invitation $existing_invite The invitation to be deleted.
     3507                 */
     3508                do_action( 'bp_members_invitations_revoked_invitation', $existing_invite );
     3509        } else {
     3510                /**
     3511                 * Fires after the deletion of a sent and accepted community invite.
     3512                 *
     3513                 * @since 8.0.0
     3514                 *
     3515                 * @param BP_Invitation $existing_invite The invitation to be deleted.
     3516                 */
     3517                do_action( 'bp_members_invitations_deleted_invitation', $existing_invite );
     3518        }
     3519
     3520        return $success;
     3521}
     3522
     3523/**
     3524 * Delete a membership invitation.
     3525 *
     3526 * @since 8.0.0
     3527 *
     3528 * @param intring $args {
     3529 *     Array of arguments.
     3530 *     @type int|array $id            Id(s) of the invitation(s) to remove.
     3531 *     @type int       $invitee_email Email address of the user being invited.
     3532 *     @type int       $network_id    ID of the network to which the user is being invited.
     3533 *     @type int       $inviter_id    ID of the inviting user.
     3534 *     @type int       $accepted      Whether the invitation has been accepted yet.
     3535 *     @type int       $invite_sent   Whether the invitation has been sent yet.
     3536 * }
     3537 * @return bool True if all were deleted.
     3538 */
     3539function bp_members_invitations_delete_invites( $args ) {
     3540        $r = bp_parse_args( $args, array(
     3541                'id'            => false,
     3542                'invitee_email' => '',
     3543                'network_id'    => get_current_network_id(),
     3544                'inviter_id'    => null,
     3545                'accepted'      => null,
     3546                'invite_sent'   => null
     3547        ), 'members_invitations_delete_invites' );
     3548
     3549        $inv_args = array(
     3550                'id'            => $r['id'],
     3551                'invitee_email' => $r['invitee_email'],
     3552                'item_id'       => $r['network_id'],
     3553                'inviter_id'    => $r['inviter_id'],
     3554                'accepted'      => $r['accepted'],
     3555                'invite_sent'   => $r['invite_sent']
     3556        );
     3557
     3558        // Find the invitation(s).
     3559        $invites       = bp_members_invitations_get_invites( $inv_args );
     3560        $total_count   = count( $invites );
     3561
     3562        // Loop through, deleting each invitation.
     3563        $deleted = 0;
     3564        foreach ( $invites as $invite ) {
     3565                $success = bp_members_invitations_delete_by_id( $invite->id );
     3566                if ( $success ) {
     3567                        $deleted++;
     3568                }
     3569        }
     3570
     3571        return $deleted === $total_count;
     3572}
     3573
     3574/**
     3575 * Get hash based on details of a membership invitation and the inviter.
     3576 *
     3577 * @since 8.0.0
     3578 *
     3579 * @param BP_Invitation object $invitation Invitation to create hash from.
     3580 *
     3581 * @return string $hash Calculated sha1 hash.
     3582 */
     3583function bp_members_invitations_get_hash( BP_Invitation $invitation ) {
     3584        $hash = false;
     3585
     3586        if ( ! empty( $invitation->id ) ) {
     3587                $inviter_ud = get_userdata( $invitation->inviter_id );
     3588                if ( $inviter_ud ) {
     3589                        /*
     3590                         * Use some inviter details as part of the hash so that invitations from
     3591                         * users who are subsequently marked as spam will be invalidated.
     3592                         */
     3593                        $hash = wp_hash( "{$invitation->inviter_id}:{$invitation->invitee_email}:{$inviter_ud->user_status}:{$inviter_ud->user_registered}" );
     3594                }
     3595        }
     3596
     3597        // If there's a problem, return a string that will change and thus fail.
     3598        if ( ! $hash ) {
     3599                $hash = wp_generate_password( 32, false );
     3600        }
     3601
     3602        /**
     3603         * Filters the hash calculated by the invitation details.
     3604         *
     3605         * @since 8.0.0
     3606         *
     3607         * @param string $hash Calculated sha1 hash.
     3608         * @param BP_Invitation object $invitation Invitation hash was created from.
     3609         */
     3610        return apply_filters( 'bp_members_invitations_get_hash', $hash, $invitation );
     3611}
     3612
     3613/**
     3614 * Get the current invitation specified by the $_GET parameters.
     3615 *
     3616 * @since 8.0.0
     3617 *
     3618 * @return BP_Invitation $invite Invitation specified by the $_GET parameters.
     3619 */
     3620function bp_get_members_invitation_from_request() {
     3621        $invites_class = new BP_Members_Invitation_Manager();
     3622        $invite        = $invites_class->get_by_id( 0 );
     3623
     3624        if ( bp_get_members_invitations_allowed() && ! empty( $_GET['inv'] ) ) {
     3625                // Check to make sure the passed hash matches a calculated hash.
     3626                $maybe_invite = $invites_class->get_by_id( absint( $_GET['inv'] ) );
     3627                $hash = bp_members_invitations_get_hash( $maybe_invite );
     3628                if ( $_GET['ih'] === $hash ) {
     3629                        $invite = $maybe_invite;
     3630                }
     3631        }
     3632
     3633        /**
     3634         * Filters the invitation specified by the $_GET parameters.
     3635         *
     3636         * @since 8.0.0
     3637         *
     3638         * @param BP_Invitation $invite Invitation specified by the $_GET parameters.
     3639         */
     3640        return apply_filters( 'bp_get_members_invitation_from_request', $invite );
     3641}
  • new file src/bp-members/bp-members-invitations.php

    diff --git src/bp-members/bp-members-invitations.php src/bp-members/bp-members-invitations.php
    new file mode 100644
    index 000000000..2a04c3dbd
    - +  
     1<?php
     2/**
     3 * BuddyPress Membersip Invitations
     4 *
     5 * @package BuddyPress
     6 * @subpackage MembersInvitations
     7 * @since 8.0.0
     8 */
     9
     10// Exit if accessed directly.
     11defined( 'ABSPATH' ) || exit;
     12
     13function bp_members_invitations_setup_nav() {
     14        if ( ! bp_get_members_invitations_allowed() ) {
     15                return;
     16        }
     17
     18        $user_has_access  = bp_user_has_access();
     19        $user_can_send    = bp_user_can( bp_displayed_user_id(), 'bp_members_send_invitation' );
     20        $user_has_invites = bp_members_invitations_user_has_sent_invites( bp_displayed_user_id() );
     21
     22        /* Add 'Invitations' to the main user profile navigation */
     23        bp_core_new_nav_item(
     24                array(
     25                        'name'                    => __( 'Invitations', 'buddypress' ),
     26                        'slug'                    => bp_get_members_invitations_slug(),
     27                        'position'                => 80,
     28                        'screen_function'         => 'members_screen_send_invites',
     29                        'default_subnav_slug'     => ( $user_can_send && bp_is_my_profile() ) ? 'send-invites' : 'list-invites',
     30                        'show_for_displayed_user' => $user_has_access && ( $user_can_send || $user_has_invites )
     31                )
     32        );
     33
     34        $parent_link = trailingslashit( bp_displayed_user_domain() . bp_get_members_invitations_slug() );
     35
     36        /* Create two subnav items for community invitations */
     37        bp_core_new_subnav_item(
     38                array(
     39                        'name'            => __( 'Send Invites', 'buddypress' ),
     40                        'slug'            => 'send-invites',
     41                        'parent_slug'     => bp_get_members_invitations_slug(),
     42                        'parent_url'      => $parent_link,
     43                        'screen_function' => 'members_screen_send_invites',
     44                        'position'        => 10,
     45                        'user_has_access' => $user_has_access && $user_can_send && bp_is_my_profile()
     46                )
     47        );
     48
     49        bp_core_new_subnav_item(
     50                array(
     51                        'name'            => __( 'Pending Invites', 'buddypress' ),
     52                        'slug'            => 'list-invites',
     53                        'parent_slug'     => bp_get_members_invitations_slug(),
     54                        'parent_url'      => $parent_link,
     55                        'screen_function' => 'members_screen_list_sent_invites',
     56                        'position'        => 20,
     57                        'user_has_access' => $user_has_access && ( $user_can_send || $user_has_invites )
     58                )
     59        );
     60}
     61add_action( 'bp_setup_nav', 'bp_members_invitations_setup_nav' );
     62
     63/**
     64 * When a user joins the network via an invitation, skip sending the activation email.
     65 *
     66 * @param bool   $send           Whether or not to send the activation key.
     67 * @param int    $user_id        User ID to send activation key to.
     68 * @param string $user_email     User email to send activation key to.
     69 * @param string $activation_key Activation key to be sent.
     70 * @param array  $usermeta       Miscellaneous metadata about the user (blog-specific
     71 *                               signup data, xprofile data, etc).
     72 */
     73function bp_members_invitations_cancel_activation_email( $send, $user_id, $user_email, $activation_key, $usermeta ) {
     74        $invite = bp_members_invitations_get_invites(
     75                array(
     76                        'invitee_email' => $user_email,
     77                        'invite_sent'   => 'sent'
     78                )
     79        );
     80
     81        if ( $invite ) {
     82                $send = false;
     83        }
     84
     85        return $send;
     86}
     87add_filter( 'bp_core_signup_send_activation_key', 'bp_members_invitations_cancel_activation_email', 10, 5 );
     88
     89/**
     90 * When a user joins the network via an invitation:
     91 * - mark all invitations and requests as accepted
     92 * - activate the user upon signup
     93 *
     94 * @param bool|WP_Error   $user_id       True on success, WP_Error on failure.
     95 * @param string          $user_login    Login name requested by the user.
     96 * @param string          $user_password Password requested by the user.
     97 * @param string          $user_email    Email address requested by the user.
     98 */
     99function bp_members_invitations_complete_signup( $user_id, $user_login, $user_password, $user_email ) {
     100        if ( ! $user_id ) {
     101                return;
     102        }
     103
     104        // Check to see if this signup is the result of a valid invitation.
     105        $invite = bp_get_members_invitation_from_request();
     106        if ( ! $invite->id ) {
     107                return;
     108        }
     109
     110        // Accept the invitation.
     111        $invites_class = new BP_Members_Invitation_Manager();
     112        $args          = array(
     113                'id' => $invite->id,
     114        );
     115        $invites_class->accept_invitation( $args );
     116
     117        // User has already verified their email by responding to the invitation, so we can activate.
     118        $key = bp_get_user_meta( $user_id, 'activation_key', true );
     119        if ( $key ) {
     120                /**
     121                 * Filters the activation signup.
     122                 *
     123                 * @since 1.1.0
     124                 *
     125                 * @param bool|int $value Value returned by activation.
     126                 *                        Integer on success, boolean on failure.
     127                 */
     128                $user = apply_filters( 'bp_core_activate_account', bp_core_activate_signup( $key ) );
     129
     130                // If there were errors, add a message and redirect.
     131                if ( ! empty( $user->errors ) ) {
     132                        bp_core_add_message( $user->get_error_message(), 'error' );
     133                        bp_core_redirect( trailingslashit( bp_get_root_domain() . '/' . $bp->pages->activate->slug ) );
     134                }
     135
     136                bp_core_add_message( __( 'Your account is now active!', 'buddypress' ) );
     137                bp_core_redirect( add_query_arg( 'activated', '1', bp_get_activation_page() ) );
     138        }
     139}
     140add_action( 'bp_core_signup_user', 'bp_members_invitations_complete_signup', 10, 4 );
     141
     142/**
     143 * Delete site membership invitations when an opt-out request is saved.
     144 *
     145 * @since 8.0.0
     146 *
     147 * @param BP_Optout $optout Characteristics of the opt-out just saved.
     148 */
     149function bp_members_invitations_delete_optedout_invites( $optout ) {
     150
     151        $args = array(
     152                'invitee_email' => $optout->email_address,
     153        );
     154        bp_members_invitations_delete_invites( $args );
     155}
     156add_action( 'bp_optout_after_save', 'bp_members_invitations_delete_optedout_invites' );
  • new file src/bp-members/bp-members-notifications.php

    diff --git src/bp-members/bp-members-notifications.php src/bp-members/bp-members-notifications.php
    new file mode 100644
    index 000000000..6571eb1e5
    - +  
     1<?php
     2/**
     3 * BuddyPress Members Activity Functions.
     4 *
     5 * These functions handle the recording, deleting and formatting of activity
     6 * for the user and for this specific component.
     7 *
     8 * @package BuddyPress
     9 * @subpackage MembersNotifications
     10 * @since 8.0.0
     11 */
     12
     13// Exit if accessed directly.
     14defined( 'ABSPATH' ) || exit;
     15
     16/**
     17 * Notification formatting callback for bp-members notifications.
     18 *
     19 * @since 8.0.0
     20 *
     21 * @param string $action            The kind of notification being rendered.
     22 * @param int    $item_id           The primary item ID.
     23 * @param int    $secondary_item_id The secondary item ID.
     24 * @param int    $total_items       The total number of members-related notifications
     25 *                                  waiting for the user.
     26 * @param string $format            'string' for BuddyBar-compatible notifications;
     27 *                                  'array' for WP Toolbar. Default: 'string'.
     28 * @return array|string
     29 */
     30function members_format_notifications( $action, $item_id, $secondary_item_id, $total_items, $format = 'string' ) {
     31
     32        switch ( $action ) {
     33                case 'accepted_invitation':
     34
     35                        // Set up the string and the filter.
     36                        if ( (int) $total_items > 1 ) {
     37                                $link   = bp_get_notifications_permalink();
     38                                $amount = 'multiple';
     39
     40                                // This is the inviter whose invitation was accepted.
     41                                if ( 0 !== (int) $secondary_item_id )  {
     42                                        /* translators: %d: the number of new users */
     43                                        $text = sprintf( __( '%d members accepted your membership invitations', 'buddypress' ), (int) $total_items );
     44                                // This is someone who also invited that user to join.
     45                                } else {
     46                                        /* translators: %d: the number of new users */
     47                                        $text = sprintf( __( '%d members are now members of the site', 'buddypress' ), (int) $total_items );
     48                                }
     49                        } else {
     50                                $link   = add_query_arg( 'welcome', 1, bp_core_get_user_domain( $item_id ) );
     51                                $amount = 'single';
     52
     53                                // This is the inviter whose invitation was accepted.
     54                                if ( 0 !== (int) $secondary_item_id )  {
     55                                        /* translators: %s: new user name */
     56                                        $text = sprintf( __( '%s accepted your membership invitation', 'buddypress' ),  bp_core_get_user_displayname( $item_id ) );                             // This is someone who also invited that user to join.
     57                                } else {
     58                                        /* translators: %s: new user name */
     59                                        $text = sprintf( __( '%s is now a member of the site', 'buddypress' ),  bp_core_get_user_displayname( $item_id ) );
     60                                }
     61                        }
     62                        break;
     63        }
     64
     65        // Return either an HTML link or an array, depending on the requested format.
     66        if ( 'string' == $format ) {
     67
     68                /**
     69                 * Filters the format of members notifications based on type and amount * of notifications pending.
     70                 *
     71                 * This is a variable filter that has four possible versions.
     72                 * The possible versions are:
     73                 *   - bp_members_single_accepted_invitation_notification
     74                 *   - bp_members_multiple_accepted_invitation_notification
     75                 *
     76                 * @since 8.0.0
     77                 *
     78                 * @param string|array $value             Depending on format, an HTML link to new requests profile tab or array with link and text.
     79                 * @param int          $total_items       The total number of messaging-related notifications waiting for the user.
     80                 * @param int          $item_id           The primary item ID.
     81                 * @param int          $secondary_item_id The secondary item ID.
     82                 */
     83                $return = apply_filters( 'bp_members_' . $amount . '_'. $action . '_notification', '<a href="' . esc_url( $link ) . '">' . esc_html( $text ) . '</a>', (int) $total_items, $item_id, $secondary_item_id );
     84        } else {
     85                /** This filter is documented in bp-members/bp-members-notifications.php */
     86                $return = apply_filters( 'bp_members_' . $amount . '_'. $action . '_notification', array(
     87                        'link' => $link,
     88                        'text' => $text
     89                ), (int) $total_items, $item_id, $secondary_item_id );
     90        }
     91
     92        /**
     93         * Fires at the end of the bp-members notification format callback.
     94         *
     95         * @since 8.0.0
     96         *
     97         * @param string       $action            The kind of notification being rendered.
     98         * @param int          $item_id           The primary item ID.
     99         * @param int          $secondary_item_id The secondary item ID.
     100         * @param int          $total_items       The total number of members-related notifications
     101         *                                        waiting for the user.
     102         * @param array|string $return            Notification text string or array of link and text.
     103         */
     104        do_action( 'members_format_notifications', $action, $item_id, $secondary_item_id, $total_items, $return );
     105
     106        return $return;
     107}
     108
     109/**
     110 * Notify one use that another user has accepted their site membership invitation.
     111 *
     112 * @since 8.0.0
     113 *
     114 * @param BP_Invitation $invite     Invitation that was accepted.
     115 * @param WP_user       $new_user   User who accepted the membership invite.
     116 * @param int           $inviter_id ID of the user who invited this user to the site.
     117 */
     118function bp_members_invitations_accepted_invitation_notification( $invite, $new_user, $inviter_id ) {
     119
     120        // Add "user is a member of the site" notification for other inviters.
     121        $args = array(
     122                'invitee_email' => $new_user->user_email,
     123                'accepted'      => 'all',
     124        );
     125        $invites = bp_members_invitations_get_invites( $args );
     126
     127        if ( ! $invites ) {
     128                return;
     129        }
     130        foreach ( $invites as $invite) {
     131                // Include the id of the "accepted" invitation.
     132                if ( $invite->inviter_id === $inviter_id ) {
     133                        $secondary_item_id = $invite->id;
     134                } else {
     135                        // Else don't store the invite id, so we know this is not the primary.
     136                        $secondary_item_id = 0;
     137                }
     138                $res = bp_notifications_add_notification( array(
     139                        'user_id'           => $invite->inviter_id,
     140                        'item_id'           => $new_user->ID,
     141                        'secondary_item_id' => $secondary_item_id,
     142                        'component_name'    => buddypress()->members->id,
     143                        'component_action'  => 'accepted_invitation',
     144                        'date_notified'     => bp_core_current_time(),
     145                        'is_new'            => 1,
     146                ) );
     147        }
     148}
     149add_action( 'community_membership_invite_accepted', 'bp_members_invitations_accepted_invitation_notification', 10, 3 );
     150
     151/**
     152 * Mark accepted invitation notifications as read when user visits new user profile.
     153 *
     154 *
     155 * @since 8.0.0
     156 */
     157function bp_members_mark_read_accepted_invitation_notification() {
     158        if ( false === is_singular() || false === is_user_logged_in() || ! bp_is_user() || empty( $_GET['welcome'] ) ) {
     159                return;
     160        }
     161
     162        // Mark notification as read.
     163        BP_Notifications_Notification::update(
     164                array(
     165                        'is_new'  => false
     166                ),
     167                array(
     168                        'user_id' => bp_loggedin_user_id(),
     169                        'item_id' => bp_displayed_user_id(),
     170                )
     171        );
     172}
     173add_action( 'bp_screens', 'bp_members_mark_read_accepted_invitation_notification' );
     174
     175/**
     176 * Add Friends-related settings to the Settings > Notifications page.
     177 *
     178 * @since 8.0.0
     179 */
     180function members_screen_notification_settings() {
     181
     182        // Bail early if invitations are not allowed--they are the only members notification so far.
     183        if ( ! bp_get_members_invitations_allowed () ) {
     184                return;
     185        }
     186
     187        if ( ! $allow_acceptance_emails = bp_get_user_meta( bp_displayed_user_id(), 'notification_members_invitation_accepted', true ) ) {
     188                $allow_acceptance_emails = 'yes';
     189        }
     190        ?>
     191
     192        <table class="notification-settings" id="members-notification-settings">
     193                <thead>
     194                        <tr>
     195                                <th class="icon"></th>
     196                                <th class="title"><?php _ex( 'Members', 'Member settings on notification settings page', 'buddypress' ) ?></th>
     197                                <th class="yes"><?php _e( 'Yes', 'buddypress' ) ?></th>
     198                                <th class="no"><?php _e( 'No', 'buddypress' )?></th>
     199                        </tr>
     200                </thead>
     201
     202                <tbody>
     203                        <tr id="members-notification-settings-invitation_accepted">
     204                                <td></td>
     205                                <td><?php _ex( 'Someone accepts your membership invitation', 'Member settings on notification settings page', 'buddypress' ) ?></td>
     206                                <td class="yes"><input type="radio" name="notifications[notification_members_invitation_accepted]" id="notification-members-invitation-accepted-yes" value="yes" <?php checked( $allow_acceptance_emails, 'yes', true ) ?>/><label for="notification-members-invitation-accepted-yes" class="bp-screen-reader-text"><?php
     207                                        /* translators: accessibility text */
     208                                        _e( 'Yes, send email', 'buddypress' );
     209                                ?></label></td>
     210                                <td class="no"><input type="radio" name="notifications[notification_members_invitation_accepted]" id="notification-members-invitation-accepted-no" value="no" <?php checked( $allow_acceptance_emails, 'no', true ) ?>/><label for="notification-members-invitation-accepted-no" class="bp-screen-reader-text"><?php
     211                                        /* translators: accessibility text */
     212                                        _e( 'No, do not send email', 'buddypress' );
     213                                ?></label></td>
     214                        </tr>
     215
     216                        <?php
     217
     218                        /**
     219                         * Fires after the last table row on the members notification screen.
     220                         *
     221                         * @since 1.0.0
     222                         */
     223                        do_action( 'members_screen_notification_settings' ); ?>
     224
     225                </tbody>
     226        </table>
     227
     228<?php
     229}
     230add_action( 'bp_notification_settings', 'members_screen_notification_settings' );
  • src/bp-members/bp-members-template.php

    diff --git src/bp-members/bp-members-template.php src/bp-members/bp-members-template.php
    index 541b27479..b7f9c9afb 100644
    function bp_activate_slug() { 
    274274                return apply_filters( 'bp_get_activate_slug', $slug );
    275275        }
    276276
     277/**
     278 * Output the members invitation pane slug.
     279 *
     280 * @since 8.0.0
     281 *
     282 */
     283function bp_members_invitations_slug() {
     284        echo bp_get_members_invitations_slug();
     285}
     286        /**
     287         * Return the members invitations root slug.
     288         *
     289         * @since 8.0.0
     290         *
     291         * @return string
     292         */
     293        function bp_get_members_invitations_slug() {
     294
     295                /**
     296                 * Filters the Members invitations pane root slug.
     297                 *
     298                 * @since 8.0.0
     299                 *
     300                 * @param string $slug Members invitations pane root slug.
     301                 */
     302                return apply_filters( 'bp_get_members_invitations_slug', _x( 'invitations', 'Member profile invitations pane URL base', 'buddypress' ) );
     303        }
     304
    277305/**
    278306 * Initialize the members loop.
    279307 *
    function bp_signup_email_value() { 
    23812409         */
    23822410        function bp_get_signup_email_value() {
    23832411                $value = '';
    2384                 if ( isset( $_POST['signup_email'] ) )
     2412                if ( isset( $_POST['signup_email'] ) ) {
    23852413                        $value = $_POST['signup_email'];
     2414                } else if ( bp_get_members_invitations_allowed() ) {
     2415                        $invite = bp_get_members_invitation_from_request();
     2416                        if ( $invite ) {
     2417                                $value = $invite->invitee_email;
     2418                        }
     2419                }
    23862420
    23872421                /**
    23882422                 * Filters the email address submitted during signup.
    function bp_signup_allowed() { 
    27552789                return apply_filters( 'bp_get_signup_allowed', (bool) bp_get_option( 'users_can_register' ) );
    27562790        }
    27572791
     2792/**
     2793 * Are users allowed to invite users to join this site?
     2794 *
     2795 * @since 8.0.0
     2796 *
     2797 * @return bool
     2798 */
     2799function bp_get_members_invitations_allowed() {
     2800        /**
     2801         * Filters whether or not community invitations are allowed.
     2802         *
     2803         * @since 8.0.0
     2804         *
     2805         * @param bool $allowed Whether or not community invitations are allowed.
     2806         */
     2807        return apply_filters( 'bp_get_members_invitations_allowed', bp_is_active( 'members', 'invitations' ) && (bool) bp_get_option( 'bp-enable-members-invitations' ) );
     2808}
     2809
    27582810/**
    27592811 * Hook member activity feed to <head>.
    27602812 *
    function bp_avatar_delete_link() { 
    28692921                 */
    28702922                return apply_filters( 'bp_get_avatar_delete_link', wp_nonce_url( bp_displayed_user_domain() . bp_get_profile_slug() . '/change-avatar/delete-avatar/', 'bp_delete_avatar_link' ) );
    28712923        }
     2924
     2925
     2926/** The Members Invitations Loop ******************************************************************/
     2927
     2928/**
     2929 * Initialize the community invitations loop.
     2930 *
     2931 * Based on the $args passed, bp_has_invitations() populates
     2932 * buddypress()->invitations->query_loop global, enabling the use of BP
     2933 * templates and template functions to display a list of invitations.
     2934 *
     2935 * @since 8.0.0
     2936 *
     2937 * @param array|string $args {
     2938 *     Arguments for limiting the contents of the invitations loop. Can be
     2939 *     passed as an associative array, or as a URL query string.
     2940 *
     2941 *     See {@link BP_Invitations_Invitation::get()} for detailed
     2942 *     information on the arguments.  In addition, also supports:
     2943 *
     2944 *     @type int    $max      Optional. Max items to display. Default: false.
     2945 *     @type string $page_arg URL argument to use for pagination.
     2946 *                            Default: 'ipage'.
     2947 * }
     2948 * @return bool
     2949 */
     2950function bp_has_members_invitations( $args = '' ) {
     2951
     2952        // Get the user ID.
     2953        if ( bp_displayed_user_id() ) {
     2954                $user_id = bp_displayed_user_id();
     2955        } else {
     2956                $user_id = bp_loggedin_user_id();
     2957        }
     2958
     2959        // Set the search terms (by default an empty string to get all notifications)
     2960        $search_terms = '';
     2961
     2962        if ( isset( $_REQUEST['s'] ) ) {
     2963                $search_terms = stripslashes( $_REQUEST['s'] );
     2964        }
     2965
     2966        // Parse the args.
     2967        $r = bp_parse_args( $args, array(
     2968                'id'                => false,
     2969                'inviter_id'        => $user_id,
     2970                'invitee_email'     => false,
     2971                'item_id'           => false,
     2972                'type'              => 'invite',
     2973                'invite_sent'       => 'all',
     2974                'accepted'          => 'pending',
     2975                'search_terms'      => $search_terms,
     2976                'order_by'          => 'date_modified',
     2977                'sort_order'        => 'DESC',
     2978                'page'              => 1,
     2979                'per_page'          => 25,
     2980                'fields'            => 'all',
     2981
     2982                // These are additional arguments that are not available in
     2983                // BP_Invitations_Invitation::get().
     2984                'page_arg'          => 'ipage',
     2985        ), 'has_community_invitations' );
     2986
     2987        // Get the notifications.
     2988        $query_loop = new BP_Members_Invitations_Template( $r );
     2989
     2990        // Setup the global query loop.
     2991        buddypress()->members->invitations->query_loop = $query_loop;
     2992
     2993        /**
     2994         * Filters whether or not the user has network invitations to display.
     2995         *
     2996         * @since 8.0.0
     2997         *
     2998         * @param bool                      $value      Whether or not there are network invitations to display.
     2999         * @param BP_Notifications_Template $query_loop BP_Members_Invitations_Template object instance.
     3000         * @param array                     $r          Array of arguments passed into the BP_Members_Invitations_Template class.
     3001         */
     3002        return apply_filters( 'bp_has_members_invitations', $query_loop->has_invitations(), $query_loop, $r );
     3003}
     3004
     3005/**
     3006 * Get the network invitations returned by the template loop.
     3007 *
     3008 * @since 8.0.0
     3009 *
     3010 * @return array List of network invitations.
     3011 */
     3012function bp_the_members_invitations() {
     3013        return buddypress()->members->invitations->query_loop->invitations();
     3014}
     3015
     3016/**
     3017 * Get the current network invitation object in the loop.
     3018 *
     3019 * @since 8.0.0
     3020 *
     3021 * @return object The current network invitation within the loop.
     3022 */
     3023function bp_the_members_invitation() {
     3024        return buddypress()->members->invitations->query_loop->the_invitation();
     3025}
     3026
     3027/**
     3028 * Output the pagination count for the current network invitations loop.
     3029 *
     3030 * @since 8.0.0
     3031 */
     3032function bp_members_invitations_pagination_count() {
     3033        echo bp_get_members_invitations_pagination_count();
     3034}
     3035        /**
     3036         * Return the pagination count for the current network invitation loop.
     3037         *
     3038         * @since 8.0.0
     3039         *
     3040         * @return string HTML for the pagination count.
     3041         */
     3042        function bp_get_members_invitations_pagination_count() {
     3043                $query_loop = buddypress()->members->invitations->query_loop;
     3044                $start_num  = intval( ( $query_loop->pag_page - 1 ) * $query_loop->pag_num ) + 1;
     3045                $from_num   = bp_core_number_format( $start_num );
     3046                $to_num     = bp_core_number_format( ( $start_num + ( $query_loop->pag_num - 1 ) > $query_loop->total_invitation_count ) ? $query_loop->total_invitation_count : $start_num + ( $query_loop->pag_num - 1 ) );
     3047                $total      = bp_core_number_format( $query_loop->total_invitation_count );
     3048
     3049                if ( 1 == $query_loop->total_invitation_count ) {
     3050                        $pag = __( 'Viewing 1 invitation', 'buddypress' );
     3051                } else {
     3052                        /* translators: 1: notification from number. 2: notification to number. 3: total notifications. */
     3053                        $pag = sprintf( _n( 'Viewing %1$s - %2$s of %3$s invitation', 'Viewing %1$s - %2$s of %3$s invitations', $query_loop->total_invitation_count, 'buddypress' ), $from_num, $to_num, $total );
     3054                }
     3055
     3056                /**
     3057                 * Filters the pagination count for the current network invitation loop.
     3058                 *
     3059                 * @since 8.0.0
     3060                 *
     3061                 * @param string $pag HTML for the pagination count.
     3062                 */
     3063                return apply_filters( 'bp_get_members_invitations_pagination_count', $pag );
     3064        }
     3065
     3066/**
     3067 * Output the pagination links for the current network invitation loop.
     3068 *
     3069 * @since 8.0.0
     3070 */
     3071function bp_members_invitations_pagination_links() {
     3072        echo bp_get_members_invitations_pagination_links();
     3073}
     3074        /**
     3075         * Return the pagination links for the current network invitations loop.
     3076         *
     3077         * @since 8.0.0
     3078         *
     3079         * @return string HTML for the pagination links.
     3080         */
     3081        function bp_get_members_invitations_pagination_links() {
     3082
     3083                /**
     3084                 * Filters the pagination links for the current network invitations loop.
     3085                 *
     3086                 * @since 8.0.0
     3087                 *
     3088                 * @param string $pag_links HTML for the pagination links.
     3089                 */
     3090                return apply_filters( 'bp_get_members_invitations_pagination_links', buddypress()->members->invitations->query_loop->pag_links );
     3091        }
     3092
     3093/**
     3094 * Output the requested property of the invitation currently being iterated on.
     3095 *
     3096 * @since 8.0.0
     3097 *
     3098 * @param string $property The name of the property to display.
     3099 * @param string $context  The context of display.
     3100 *                         Possible values are 'attribute' and 'html'.
     3101 */
     3102function bp_the_members_invitation_property( $property = '', $context = 'html' ) {
     3103        if ( ! $property ) {
     3104                return;
     3105        }
     3106
     3107        /**
     3108         * Use this filter to sanitize the output.
     3109         *
     3110         * @since 8.0.0
     3111         *
     3112         * @param int|string $value    The value for the requested property.
     3113         * @param string     $property The name of the requested property.
     3114         * @param string     $context  The context of display.
     3115         */
     3116        echo apply_filters( 'bp_the_members_invitation_property', bp_get_the_members_invitation_property( $property ), $property, $context );
     3117}
     3118        /**
     3119         * Return the value for a property of the network invitation currently being iterated on.
     3120         *
     3121         * @since 8.0.0
     3122         *
     3123         * @return int ID of the current network invitation.
     3124         */
     3125        function bp_get_the_members_invitation_property( $property = 'id' ) {
     3126
     3127                switch ( $property ) {
     3128                        case 'id':
     3129                        case 'user_id':
     3130                        case 'item_id':
     3131                        case 'secondary_item_id':
     3132                        case 'invite_sent':
     3133                        case 'accepted':
     3134                                $value = 0;
     3135                                break;
     3136                        case 'invitee_email':
     3137                        case 'type':
     3138                        case 'content':
     3139                        case 'date_modified':
     3140                                $value = '';
     3141                                break;
     3142                        default:
     3143                                // A known property has not been specified.
     3144                                $property = null;
     3145                                $value = '';
     3146                                break;
     3147                }
     3148
     3149                if ( isset( buddypress()->members->invitations->query_loop->invitation->{$property} ) ) {
     3150                        $value = buddypress()->members->invitations->query_loop->invitation->{$property};
     3151                }
     3152
     3153                /**
     3154                 * Filters the property of the network invitation currently being iterated on.
     3155                 *
     3156                 * @since 8.0.0
     3157                 *
     3158                 * @param int|string $value Property value of the network invitation being iterated on.
     3159                 */
     3160                return apply_filters( 'bp_get_the_members_invitation_property_' . $property, $value );
     3161        }
     3162
     3163/**
     3164 * Output the action links for the current invitation.
     3165 *
     3166 * @since 8.0.0
     3167 *
     3168 * @param array|string $args Array of arguments.
     3169 */
     3170function bp_the_members_invitation_action_links( $args = '' ) {
     3171        echo bp_get_the_members_invitation_action_links( $args );
     3172}
     3173        /**
     3174         * Return the action links for the current invitation.
     3175         *
     3176         * @since 8.0.0
     3177         *
     3178         * @param array|string $args {
     3179         *     @type string $before  HTML before the links.
     3180         *     @type string $after   HTML after the links.
     3181         *     @type string $sep     HTML between the links.
     3182         *     @type array  $links   Array of links to implode by 'sep'.
     3183         *     @type int    $user_id User ID to fetch action links for. Defaults to displayed user ID.
     3184         * }
     3185         * @return string HTML links for actions to take on single notifications.
     3186         */
     3187        function bp_get_the_members_invitation_action_links( $args = '' ) {
     3188                // Set default user ID to use.
     3189                $inviter_id = isset( $args['inviter_id'] ) ? $args['inviter_id'] : bp_displayed_user_id();
     3190
     3191                // Parse.
     3192                $r = wp_parse_args( $args, array(
     3193                        'before' => '',
     3194                        'after'  => '',
     3195                        'sep'    => ' | ',
     3196                        'links'  => array(
     3197                                bp_get_the_members_invitation_resend_link( $inviter_id ),
     3198                                bp_get_the_members_invitation_delete_link( $inviter_id )
     3199                        )
     3200                ) );
     3201
     3202                // Build the links.
     3203                $retval = $r['before'] . implode( $r['sep'], $r['links'] ) . $r['after'];
     3204
     3205                /**
     3206                 * Filters the action links for the current invitation.
     3207                 *
     3208                 * @since 8.0.0
     3209                 *
     3210                 * @param string $retval HTML links for actions to take on single invitation.
     3211                 * @param array  $r      Array of parsed arguments.
     3212                 */
     3213                return apply_filters( 'bp_get_the_members_invitation_action_links', $retval, $r );
     3214        }
     3215
     3216/**
     3217 * Output the resend link for the current invitation.
     3218 *
     3219 * @since 8.0.0
     3220 *
     3221 * @param int $user_id The user ID.
     3222 */
     3223function bp_the_members_invitations_resend_link( $user_id = 0 ) {
     3224        echo bp_get_the_members_invitation_delete_link( $user_id );
     3225}
     3226        /**
     3227         * Return the resend link for the current notification.
     3228         *
     3229         * @since 8.0.0
     3230         *
     3231         * @param int $user_id The user ID.
     3232         * @return string
     3233         */
     3234        function bp_get_the_members_invitation_resend_link( $user_id = 0 ) {
     3235                // Set default user ID to use.
     3236                $user_id = 0 === $user_id ? bp_displayed_user_id() : $user_id;
     3237
     3238                // Don't allow resending of accepted invitations.
     3239                if ( bp_get_the_members_invitation_property( 'accepted' ) ) {
     3240                        return;
     3241                }
     3242
     3243                $retval = sprintf( '<a href="%1$s" class="resend secondary confirm bp-tooltip">%2$s</a>', esc_url( bp_get_the_members_invitations_resend_url( $user_id ) ), __( 'Resend', 'buddypress' ) );
     3244
     3245                /**
     3246                 * Filters the resend link for the current invitation.
     3247                 *
     3248                 * @since 8.0.0
     3249                 *
     3250                 * @param string $retval  HTML for the delete link for the current notification.
     3251                 * @param int    $user_id The user ID.
     3252                 */
     3253                return apply_filters( 'bp_get_the_members_invitation_resend_link', $retval, $user_id );
     3254        }
     3255
     3256/**
     3257 * Output the URL used for resending a single invitation.
     3258 *
     3259 * Since this function directly outputs a URL, it is escaped.
     3260 *
     3261 * @since 8.0.0
     3262 *
     3263 * @param int $user_id The user ID.
     3264 */
     3265function bp_the_members_invitations_resend_url( $user_id = 0 ) {
     3266        echo esc_url( bp_get_the_members_invitations_resend_url( $user_id ) );
     3267}
     3268        /**
     3269         * Return the URL used for resending a single invitation.
     3270         *
     3271         * @since 8.0.0
     3272         *
     3273         * @param int $user_id The user ID.
     3274         * @return string
     3275         */
     3276        function bp_get_the_members_invitations_resend_url( $user_id = 0 ) {
     3277                // Set default user ID to use.
     3278                $user_id = 0 === $user_id ? bp_displayed_user_id() : $user_id;
     3279                $link = bp_get_members_invitations_list_invites_permalink( $user_id );
     3280
     3281                // Get the ID.
     3282                $id = bp_get_the_members_invitation_property( 'id' );
     3283
     3284                // Get the args to add to the URL.
     3285                $args = array(
     3286                        'action'        => 'resend',
     3287                        'invitation_id' => $id
     3288                );
     3289
     3290                // Add the args.
     3291                $url = add_query_arg( $args, $link );
     3292
     3293                // Add the nonce.
     3294                $url = wp_nonce_url( $url, 'bp_network_invitation_resend_' . $id );
     3295
     3296                /**
     3297                 * Filters the URL used for resending a single invitation.
     3298                 *
     3299                 * @since 8.0.0
     3300                 *
     3301                 * @param string $url     URL used for deleting a single invitation.
     3302                 * @param int    $user_id The user ID.
     3303                 */
     3304                return apply_filters( 'bp_get_the_members_invitations_resend_url', $url, $user_id );
     3305        }
     3306
     3307/**
     3308 * Output the delete link for the current invitation.
     3309 *
     3310 * @since 8.0.0
     3311 *
     3312 * @param int $user_id The user ID.
     3313 */
     3314function bp_the_members_invitations_delete_link( $user_id = 0 ) {
     3315        echo bp_get_the_members_invitation_delete_link( $user_id );
     3316}
     3317        /**
     3318         * Return the delete link for the current invitation.
     3319         *
     3320         * @since 8.0.0
     3321         *
     3322         * @param int $user_id The user ID.
     3323         * @return string
     3324         */
     3325        function bp_get_the_members_invitation_delete_link( $user_id = 0 ) {
     3326                // Set default user ID to use.
     3327                $user_id = 0 === $user_id ? bp_displayed_user_id() : $user_id;
     3328
     3329                // Modify the message for accepted/not accepted invitatons.
     3330                if ( bp_get_the_members_invitation_property( 'accepted' ) ) {
     3331                        $message = __( 'Delete', 'buddypress' );
     3332                } else {
     3333                        $message = __( 'Cancel', 'buddypress' );
     3334                }
     3335
     3336                $retval = sprintf(
     3337                        '<a href="%1$s" class="delete secondary confirm bp-tooltip">%2$s</a>',
     3338                        esc_url( bp_get_the_members_invitations_delete_url( $user_id ) ),
     3339                        esc_html( $message )
     3340                );
     3341
     3342                /**
     3343                 * Filters the delete link for the current invitation.
     3344                 *
     3345                 * @since 8.0.0
     3346                 *
     3347                 * @param string $retval  HTML for the delete link for the current notification.
     3348                 * @param int    $user_id The user ID.
     3349                 */
     3350                return apply_filters( 'bp_get_the_members_invitation_delete_link', $retval, $user_id );
     3351        }
     3352
     3353/**
     3354 * Output the URL used for deleting a single invitation.
     3355 *
     3356 * Since this function directly outputs a URL, it is escaped.
     3357 *
     3358 * @since 8.0.0
     3359 *
     3360 * @param int $user_id The user ID.
     3361 */
     3362function bp_the_members_invitations_delete_url( $user_id = 0 ) {
     3363        echo esc_url( bp_get_the_members_invitations_delete_url( $user_id ) );
     3364}
     3365        /**
     3366         * Return the URL used for deleting a single invitation.
     3367         *
     3368         * @since 8.0.0
     3369         *
     3370         * @param int $user_id The user ID.
     3371         * @return string
     3372         */
     3373        function bp_get_the_members_invitations_delete_url( $user_id = 0 ) {
     3374                // Set default user ID to use.
     3375                $user_id = 0 === $user_id ? bp_displayed_user_id() : $user_id;
     3376                $link = bp_get_members_invitations_list_invites_permalink( $user_id );
     3377
     3378                // Get the ID.
     3379                $id = bp_get_the_members_invitation_property( 'id' );
     3380
     3381                // Get the args to add to the URL.
     3382                $args = array(
     3383                        'action'        => 'cancel',
     3384                        'invitation_id' => $id
     3385                );
     3386
     3387                // Add the args.
     3388                $url = add_query_arg( $args, $link );
     3389
     3390                // Add the nonce.
     3391                $url = wp_nonce_url( $url, 'bp_members_invitations_cancel_' . $id );
     3392
     3393                /**
     3394                 * Filters the URL used for deleting a single invitation.
     3395                 *
     3396                 * @since 8.0.0
     3397                 *
     3398                 * @param string $url     URL used for deleting a single invitation.
     3399                 * @param int    $user_id The user ID.
     3400                 */
     3401                return apply_filters( 'bp_get_the_members_invitations_delete_url', $url, $user_id );
     3402        }
     3403
     3404/**
     3405 * Output the members invitations list permalink for a user.
     3406 *
     3407 * @since 8.0.0
     3408 *
     3409 * @param int $user_id The user ID.
     3410 */
     3411function bp_members_invitations_list_invites_permalink( $user_id = 0 ) {
     3412        echo bp_get_members_invitations_list_invites_permalink( $user_id );
     3413}
     3414        /**
     3415         * Return the members invitations list permalink for a user.
     3416         *
     3417         * @since 8.0.0
     3418         *
     3419         * @return string Members invitations list permalink for a user.
     3420         */
     3421        function bp_get_members_invitations_list_invites_permalink( $user_id = 0 ) {
     3422                if ( 0 === $user_id ) {
     3423                        $user_id = bp_loggedin_user_id();
     3424                        $domain  = bp_loggedin_user_domain();
     3425                } else {
     3426                        $domain = bp_core_get_user_domain( (int) $user_id );
     3427                }
     3428
     3429                $retval = trailingslashit( $domain . bp_get_members_invitations_slug() . '/list-invites' );
     3430
     3431                /**
     3432                 * Filters the members invitations list permalink for a user.
     3433                 *
     3434                 * @since 8.0.0
     3435                 *
     3436                 * @param string $retval  Permalink for the sent invitation list screen.
     3437                 * @param int    $user_id The user ID.
     3438                 */
     3439                return apply_filters( 'bp_get_members_invitations_list_invites_permalink', $retval, $user_id );
     3440        }
     3441
     3442/**
     3443 * Output the send invitation permalink for a user.
     3444 *
     3445 * @since 8.0.0
     3446 *
     3447 * @param int $user_id The user ID.
     3448 */
     3449function bp_members_invitations_send_invites_permalink( $user_id = 0 ) {
     3450        echo bp_get_members_invitations_send_invites_permalink( $user_id );
     3451}
     3452        /**
     3453         * Return the send invitations permalink.
     3454         *
     3455         * @since 8.0.0
     3456         *
     3457         * @param int $user_id The user ID.
     3458         * @return string      The send invitations permalink.
     3459         */
     3460        function bp_get_members_invitations_send_invites_permalink( $user_id = 0 ) {
     3461                if ( 0 === $user_id ) {
     3462                        $user_id = bp_loggedin_user_id();
     3463                        $domain  = bp_loggedin_user_domain();
     3464                } else {
     3465                        $domain = bp_core_get_user_domain( (int) $user_id );
     3466                }
     3467
     3468                $retval = trailingslashit( $domain . bp_get_members_invitations_slug() . '/send-invites' );
     3469
     3470                /**
     3471                 * Filters the send invitations permalink.
     3472                 *
     3473                 * @since 8.0.0
     3474                 *
     3475                 * @param string $retval  Permalink for the sent invitation list screen.
     3476                 * @param int    $user_id The user ID.
     3477                 */
     3478                return apply_filters( 'bp_get_members_invitations_send_invites_permalink', $retval, $user_id );
     3479        }
  • 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 64903c107..8c1d68b46 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_invites_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 { 
    219221                        // Registration is turned on.
    220222                        add_action( 'update_site_option_registration',  array( $this, 'multisite_registration_on' ),   10, 2 );
    221223                        add_action( 'update_option_users_can_register', array( $this, 'single_site_registration_on' ), 10, 2 );
     224
     225                        // Member invitations are enabled.
     226                        if ( bp_is_network_activated() ) {
     227                                add_action( 'update_site_option_bp-enable-members-invitations', array( $this, 'multisite_registration_on' ), 10, 2 );
     228                        } else {
     229                                add_action( 'update_option_bp-enable-members-invitations', array( $this, 'single_site_registration_on' ), 10, 2 );
     230                        }
    222231                }
    223232
    224233                /** Users List - Members Types ***************************************
    class BP_Members_Admin { 
    248257         * @param string $value
    249258         */
    250259        public function multisite_registration_on( $option_name, $value ) {
    251                 if ( 'user' === $value || 'all' === $value ) {
     260                // Is registration enabled or are network invitations enabled?
     261                if ( ( 'user' === $value || 'all' === $value )
     262                        || bp_get_members_invitations_allowed() ) {
    252263                        bp_core_add_page_mappings( array(
    253264                                'register' => 1,
    254265                                'activate' => 1
    class BP_Members_Admin { 
    266277         */
    267278        public function single_site_registration_on( $old_value, $value ) {
    268279                // Single site.
    269                 if ( ! is_multisite() && ! empty( $value ) ) {
     280                if ( ! is_multisite() && ( ! empty( $value ) || bp_get_members_invitations_allowed() ) ) {
    270281                        bp_core_add_page_mappings( array(
    271282                                'register' => 1,
    272283                                'activate' => 1
    class BP_Members_Admin { 
    490501                        );
    491502                }
    492503
     504                // For consistency with non-Multisite, we add a Tools menu in
     505                // the Network Admin as a home for our Tools panel.
     506                if ( is_multisite() && bp_core_do_network_admin() ) {
     507                        $tools_parent = 'network-tools';
     508                } else {
     509                        $tools_parent = 'tools.php';
     510                }
     511
     512                $hooks['members_invitations'] = $this->members_invites_page = add_submenu_page(
     513                        $tools_parent,
     514                        __( 'Manage Invitations',  'buddypress' ),
     515                        __( 'Manage Invitations',  'buddypress' ),
     516                        $this->capability,
     517                        'bp-members-invitations',
     518                        array( $this, 'invitations_admin' )
     519                );
     520
    493521                $edit_page         = 'user-edit';
    494522                $profile_page      = 'profile';
    495523                $this->users_page  = 'users';
    class BP_Members_Admin { 
    510538                        $this->users_page   .= '-network';
    511539                        $this->signups_page .= '-network';
    512540
    513                         $this->members_optouts_page .= '-network';
     541                        $this->members_invites_page .= '-network';
    514542                }
    515543
    516544                // Setup the screen ID's.
    class BP_Members_Admin { 
    529557                foreach ( $page_head as $head ) {
    530558                        add_action( "admin_head-{$head}", array( $this, 'profile_admin_head' ) );
    531559                }
     560
     561                // Highlight the BuddyPress tools submenu when managing invitations.
     562                add_action( "admin_head-{$this->members_invites_page}", 'bp_core_modify_admin_menu_highlight' );
    532563        }
    533564
    534565        /**
    class BP_Members_Admin { 
    598629        public function admin_head() {
    599630                remove_submenu_page( 'users.php',   'bp-profile-edit' );
    600631                remove_submenu_page( 'profile.php', 'bp-profile-edit' );
     632
     633                // Manage Invitations Tool screen is a tab of BP Tools.
     634                if ( is_network_admin() ) {
     635                        remove_submenu_page( 'network-tools', 'bp-members-invitations' );
     636                } else {
     637                        remove_submenu_page( 'tools.php', 'bp-members-invitations' );
     638                }
    601639        }
    602640
    603641        /** Community Profile *****************************************************/
    class BP_Members_Admin { 
    19051943                                        }
    19061944
    19071945                                        if ( ! empty( $_REQUEST['notdeleted'] ) ) {
     1946                                                $notdeleted         = absint( $_REQUEST['notdeleted'] );
    19081947                                                $notice['message'] .= sprintf(
    1909                                                         /* translators: %s: number of deleted signups not deleted */
    1910                                                         _nx( '%s sign-up was not deleted.', '%s sign-ups were not deleted.',
    1911                                                          absint( $_REQUEST['notdeleted'] ),
    1912                                                          'signup notdeleted',
    1913                                                          'buddypress'
     1948                                                        _nx(
     1949                                                                /* translators: %s: number of deleted signups not deleted */
     1950                                                                '%s sign-up was not deleted.', '%s sign-ups were not deleted.',
     1951                                                                $notdeleted,
     1952                                                                'signup notdeleted',
     1953                                                                'buddypress'
    19141954                                                        ),
    1915                                                         number_format_i18n( absint( $_REQUEST['notdeleted'] ) )
     1955                                                        number_format_i18n( $notdeleted )
    19161956                                                );
    19171957
    19181958                                                if ( empty( $_REQUEST['deleted'] ) ) {
    class BP_Members_Admin { 
    25662606
    25672607                return $value;
    25682608        }
     2609
     2610        /**
     2611         * Set up the signups admin page.
     2612         *
     2613         * Loaded before the page is rendered, this function does all initial
     2614         * setup, including: processing form requests, registering contextual
     2615         * help, and setting up screen options.
     2616         *
     2617         * @since 8.0.0
     2618         *
     2619         * @global $bp_members_invitations_list_table
     2620         */
     2621        public function members_invitations_admin_load() {
     2622                global $bp_members_invitations_list_table;
     2623
     2624                // Build redirection URL.
     2625                $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'] );
     2626                $doaction    = bp_admin_list_table_current_bulk_action();
     2627
     2628                /**
     2629                 * Fires at the start of the member invitations admin load.
     2630                 *
     2631                 * @since 8.0.0
     2632                 *
     2633                 * @param string $doaction Current bulk action being processed.
     2634                 * @param array  $_REQUEST Current $_REQUEST global.
     2635                 */
     2636                do_action( 'bp_members_invitations_admin_load', $doaction, $_REQUEST );
     2637
     2638                /**
     2639                 * Filters the allowed actions for use in the user signups admin page.
     2640                 *
     2641                 * @since 8.0.0
     2642                 *
     2643                 * @param array $value Array of allowed actions to use.
     2644                 */
     2645                $allowed_actions = apply_filters( 'bp_members_invitations_admin_allowed_actions', array( 'do_delete',  'do_resend' ) );
     2646
     2647                // Prepare the display of the bulk invitation action screen.
     2648                if ( ! in_array( $doaction, $allowed_actions ) ) {
     2649
     2650                        $bp_members_invitations_list_table = self::get_list_table_class( 'BP_Members_Invitations_List_Table', 'users' );
     2651
     2652                        // The per_page screen option.
     2653                        add_screen_option( 'per_page', array( 'label' => _x( 'Members Invitations', 'Members Invitations per page (screen options)', 'buddypress' ) ) );
     2654
     2655                        get_current_screen()->add_help_tab( array(
     2656                                'id'      => 'bp-members-invitations-overview',
     2657                                'title'   => __( 'Overview', 'buddypress' ),
     2658                                'content' =>
     2659                                '<p>' . __( 'This is the administration screen for member invitations on your site.', 'buddypress' ) . '</p>' .
     2660                                '<p>' . __( 'From the screen options, you can customize the displayed columns and the pagination of this screen.', 'buddypress' ) . '</p>' .
     2661                                '<p>' . __( 'You can reorder the list of invitations by clicking on the Invitee, Inviter, Date Modified, Email Sent, or Accepted column headers.', 'buddypress' ) . '</p>' .
     2662                                '<p>' . __( 'Using the search form, you can find specific invitations more easily. The Invitee Email field will be included in the search.', 'buddypress' ) . '</p>'
     2663                        ) );
     2664
     2665                        get_current_screen()->add_help_tab( array(
     2666                                'id'      => 'bp-members-invitations-actions',
     2667                                'title'   => __( 'Actions', 'buddypress' ),
     2668                                'content' =>
     2669                                '<p>' . __( 'Hovering over a row in the pending accounts list will display action links that allow you to manage pending accounts. You can perform the following actions:', 'buddypress' ) . '</p>' .
     2670                                '<ul><li>' . __( '"Send" or "Resend" takes you to the confirmation screen before being able to send or resend the invitation email to the desired pending invitee.', 'buddypress' ) . '</li>' .
     2671                                '<li>' . __( '"Delete" allows you to delete an unsent or accepted invitation from your site; "Cancel" allows you to cancel a sent, but not yet accepted, invitation. You will be asked to confirm this deletion.', 'buddypress' ) . '</li></ul>' .
     2672                                '<p>' . __( 'Bulk actions allow you to perform these actions for the selected rows.', 'buddypress' ) . '</p>'
     2673                        ) );
     2674
     2675                        // Help panel - sidebar links.
     2676                        get_current_screen()->set_help_sidebar(
     2677                                '<p><strong>' . __( 'For more information:', 'buddypress' ) . '</strong></p>' .
     2678                                '<p>' . __( '<a href="https://buddypress.org/support/">Support Forums</a>', 'buddypress' ) . '</p>'
     2679                        );
     2680
     2681                        // Add accessible hidden headings and text for the Pending Users screen.
     2682                        get_current_screen()->set_screen_reader_content( array(
     2683                                /* translators: accessibility text */
     2684                                'heading_views'      => __( 'Filter invitations list', 'buddypress' ),
     2685                                /* translators: accessibility text */
     2686                                'heading_pagination' => __( 'Invitation list navigation', 'buddypress' ),
     2687                                /* translators: accessibility text */
     2688                                'heading_list'       => __( 'Invitations list', 'buddypress' ),
     2689                        ) );
     2690
     2691                } else {
     2692                        if ( empty( $_REQUEST['invite_ids' ] ) ) {
     2693                                return;
     2694                        }
     2695                        $invite_ids = wp_parse_id_list( $_REQUEST['invite_ids' ] );
     2696
     2697                        // Handle resent invitations.
     2698                        if ( 'do_resend' == $doaction ) {
     2699
     2700                                // Nonce check.
     2701                                check_admin_referer( 'invitations_resend' );
     2702
     2703                                $success = 0;
     2704                                foreach ( $invite_ids as $invite_id ) {
     2705                                        if ( bp_members_invitation_resend_by_id( $invite_id ) ) {
     2706                                                $success++;
     2707                                        }
     2708                                }
     2709
     2710                                $query_arg = array( 'updated' => 'resent' );
     2711
     2712                                if ( ! empty( $success ) ) {
     2713                                        $query_arg['resent'] = $success;
     2714                                }
     2715
     2716                                $not_sent = count( $invite_ids ) - $success;
     2717                                if ( $not_sent > 0 ) {
     2718                                        $query_arg['notsent'] = $not_sent;
     2719                                }
     2720
     2721                                $redirect_to = add_query_arg( $query_arg, $redirect_to );
     2722
     2723                                bp_core_redirect( $redirect_to );
     2724
     2725                        // Handle invitation deletion.
     2726                        } elseif ( 'do_delete' == $doaction ) {
     2727
     2728                                // Nonce check.
     2729                                check_admin_referer( 'invitations_delete' );
     2730
     2731                                $success = 0;
     2732                                foreach ( $invite_ids as $invite_id ) {
     2733                                        if ( bp_members_invitations_delete_by_id( $invite_id ) ) {
     2734                                                $success++;
     2735                                        }
     2736                                }
     2737
     2738                                $query_arg = array( 'updated' => 'deleted' );
     2739
     2740                                if ( ! empty( $success ) ) {
     2741                                        $query_arg['deleted'] = $success;
     2742                                }
     2743
     2744                                $notdeleted = count( $invite_ids ) - $success;
     2745                                if ( $notdeleted > 0 ) {
     2746                                        $query_arg['notdeleted'] = $notdeleted;
     2747                                }
     2748
     2749                                $redirect_to = add_query_arg( $query_arg, $redirect_to );
     2750
     2751                                bp_core_redirect( $redirect_to );
     2752
     2753                        // Plugins can update other stuff from here.
     2754                        } else {
     2755                                $this->redirect = $redirect_to;
     2756
     2757                                /**
     2758                                 * Fires at end of member invitations admin load
     2759                                 * if doaction does not match any actions.
     2760                                 *
     2761                                 * @since 8.0.0
     2762                                 *
     2763                                 * @param string $doaction Current bulk action being processed.
     2764                                 * @param array  $_REQUEST Current $_REQUEST global.
     2765                                 * @param string $redirect Determined redirect url to send user to.
     2766                                 */
     2767                                do_action( 'bp_members_admin_update_invitations', $doaction, $_REQUEST, $this->redirect );
     2768
     2769                                bp_core_redirect( $this->redirect );
     2770                        }
     2771                }
     2772        }
     2773
     2774        /**
     2775         * Get admin notice when viewing the invitations management page.
     2776         *
     2777         * @since 8.0.0
     2778         *
     2779         * @return array
     2780         */
     2781        private function get_members_invitations_notice() {
     2782
     2783                // Setup empty notice for return value.
     2784                $notice = array();
     2785
     2786                // Updates.
     2787                if ( ! empty( $_REQUEST['updated'] ) ) {
     2788                        switch ( $_REQUEST['updated'] ) {
     2789                                case 'resent':
     2790                                        $notice = array(
     2791                                                'class'   => 'updated',
     2792                                                'message' => ''
     2793                                        );
     2794
     2795                                        if ( ! empty( $_REQUEST['resent'] ) ) {
     2796                                                $resent             = absint( $_REQUEST['resent'] );
     2797                                                $notice['message'] .= sprintf(
     2798                                                        _nx(
     2799                                                                /* translators: %s: number of invitation emails sent */
     2800                                                                '%s invtitation email successfully sent! ', '%s invitation emails successfully sent! ',
     2801                                                                $resent,
     2802                                                                'members invitation resent',
     2803                                                                'buddypress'
     2804                                                        ),
     2805                                                        number_format_i18n( $resent )
     2806                                                );
     2807                                        }
     2808
     2809                                        if ( ! empty( $_REQUEST['notsent'] ) ) {
     2810                                                $notsent            = absint( $_REQUEST['notsent'] );
     2811                                                $notice['message'] .= sprintf(
     2812                                                        _nx(
     2813                                                                /* translators: %s: number of unsent invitation emails */
     2814                                                                '%s invitation email was not sent.', '%s invitation emails were not sent.',
     2815                                                                $notsent,
     2816                                                                'members invitation notsent',
     2817                                                                'buddypress'
     2818                                                        ),
     2819                                                        number_format_i18n( $notsent )
     2820                                                );
     2821
     2822                                                if ( empty( $_REQUEST['resent'] ) ) {
     2823                                                        $notice['class'] = 'error';
     2824                                                }
     2825                                        }
     2826
     2827                                        break;
     2828
     2829                                case 'deleted':
     2830                                        $notice = array(
     2831                                                'class'   => 'updated',
     2832                                                'message' => ''
     2833                                        );
     2834
     2835                                        if ( ! empty( $_REQUEST['deleted'] ) ) {
     2836                                                $deleted            = absint( $_REQUEST['deleted'] );
     2837                                                $notice['message'] .= sprintf(
     2838                                                        _nx(
     2839                                                                /* translators: %s: number of deleted invitations */
     2840                                                                '%s invitation successfully deleted!', '%s invitations successfully deleted!',
     2841                                                                $deleted,
     2842                                                                'members invitation deleted',
     2843                                                                'buddypress'
     2844                                                        ),
     2845                                                        number_format_i18n( $deleted )
     2846                                                );
     2847                                        }
     2848
     2849                                        if ( ! empty( $_REQUEST['notdeleted'] ) ) {
     2850                                                $notdeleted         = absint( $_REQUEST['notdeleted'] );
     2851                                                $notice['message'] .= sprintf(
     2852                                                        _nx(
     2853                                                                /* translators: %s: number of invitations that failed to be deleted */
     2854                                                                '%s invitation was not deleted.', '%s invitations were not deleted.',
     2855                                                                $notdeleted,
     2856                                                                'members invitation notdeleted',
     2857                                                                'buddypress'
     2858                                                        ),
     2859                                                        number_format_i18n( $notdeleted )
     2860                                                );
     2861
     2862                                                if ( empty( $_REQUEST['deleted'] ) ) {
     2863                                                        $notice['class'] = 'error';
     2864                                                }
     2865                                        }
     2866
     2867                                        break;
     2868                        }
     2869                }
     2870
     2871                // Errors.
     2872                if ( ! empty( $_REQUEST['error'] ) ) {
     2873                        switch ( $_REQUEST['error'] ) {
     2874                                case 'do_resend':
     2875                                        $notice = array(
     2876                                                'class'   => 'error',
     2877                                                'message' => esc_html__( 'There was a problem sending the invitation emails. Please try again.', 'buddypress' ),
     2878                                        );
     2879                                        break;
     2880
     2881                                case 'do_delete':
     2882                                        $notice = array(
     2883                                                'class'   => 'error',
     2884                                                'message' => esc_html__( 'There was a problem deleting invitations. Please try again.', 'buddypress' ),
     2885                                        );
     2886                                        break;
     2887                        }
     2888                }
     2889
     2890                return $notice;
     2891        }
     2892
     2893        /**
     2894         * Member invitations admin page router.
     2895         *
     2896         * Depending on the context, display
     2897         * - the list of invitations,
     2898         * - or the delete confirmation screen,
     2899         * - or the "resend" email confirmation screen.
     2900         *
     2901         * Also prepare the admin notices.
     2902         *
     2903         * @since 8.0.0
     2904         */
     2905        public function invitations_admin() {
     2906                $doaction = bp_admin_list_table_current_bulk_action();
     2907
     2908                // Prepare notices for admin.
     2909                $notice = $this->get_members_invitations_notice();
     2910
     2911                // Display notices.
     2912                if ( ! empty( $notice ) ) :
     2913                        if ( 'updated' === $notice['class'] ) : ?>
     2914
     2915                                <div id="message" class="<?php echo esc_attr( $notice['class'] ); ?> notice is-dismissible">
     2916
     2917                        <?php else: ?>
     2918
     2919                                <div class="<?php echo esc_attr( $notice['class'] ); ?> notice is-dismissible">
     2920
     2921                        <?php endif; ?>
     2922
     2923                                <p><?php echo $notice['message']; ?></p>
     2924                        </div>
     2925
     2926                <?php endif;
     2927
     2928                // Show the proper screen.
     2929                switch ( $doaction ) {
     2930                        case 'delete' :
     2931                        case 'resend' :
     2932                                $this->invitations_admin_manage( $doaction );
     2933                                break;
     2934
     2935                        default:
     2936                                $this->invitations_admin_index();
     2937                                break;
     2938                }
     2939        }
     2940
     2941        /**
     2942         * This is the list of invitations.
     2943         *
     2944         * @since 8.0.0
     2945         *
     2946         * @global $plugin_page
     2947         * @global $bp_members_invitations_list_table
     2948         */
     2949        public function invitations_admin_index() {
     2950                global $plugin_page, $bp_members_invitations_list_table;
     2951
     2952                $usersearch = ! empty( $_REQUEST['s'] ) ? stripslashes( $_REQUEST['s'] ) : '';
     2953
     2954                // Prepare the group items for display.
     2955                $bp_members_invitations_list_table->prepare_items();
     2956
     2957                if ( is_network_admin() ) {
     2958                        $form_url = network_admin_url( 'admin.php' );
     2959                } else {
     2960                        $form_url = bp_get_admin_url( 'tools.php' );
     2961                }
     2962
     2963                $form_url = add_query_arg(
     2964                        array(
     2965                                'page' => 'bp-members-invitations',
     2966                        ),
     2967                        $form_url
     2968                );
     2969
     2970                $search_form_url = remove_query_arg(
     2971                        array(
     2972                                'action',
     2973                                'deleted',
     2974                                'notdeleted',
     2975                                'error',
     2976                                'updated',
     2977                                'delete',
     2978                                'activate',
     2979                                'activated',
     2980                                'notactivated',
     2981                                'resend',
     2982                                'resent',
     2983                                'notresent',
     2984                                'do_delete',
     2985                                'do_activate',
     2986                                'do_resend',
     2987                                'action2',
     2988                                '_wpnonce',
     2989                                'invite_ids'
     2990                        ), $_SERVER['REQUEST_URI']
     2991                );
     2992
     2993                ?>
     2994
     2995                <div class="wrap">
     2996                        <h1 class="wp-heading-inline"><?php esc_html_e( 'BuddyPress tools', 'buddypress' ); ?></h1>
     2997                        <hr class="wp-header-end">
     2998
     2999                        <h2 class="nav-tab-wrapper"><?php bp_core_admin_tabs( __( 'Manage Invitations', 'buddypress' ), 'tools' ); ?></h2>
     3000
     3001                        <?php
     3002                        if ( $usersearch ) {
     3003                                printf( '<span class="subtitle">' . __( 'Search results for &#8220;%s&#8221;', 'buddypress' ) . '</span>', esc_html( $usersearch ) );
     3004                        }
     3005                        ?>
     3006
     3007                        <hr class="wp-header-end">
     3008
     3009                        <?php // Display each invitation on its own row. ?>
     3010                        <?php $bp_members_invitations_list_table->views(); ?>
     3011
     3012                        <form id="bp-members-invitations-search-form" action="<?php echo esc_url( $search_form_url ) ;?>">
     3013                                <input type="hidden" name="page" value="<?php echo esc_attr( $plugin_page ); ?>" />
     3014                                <?php $bp_members_invitations_list_table->search_box( __( 'Search Invitations', 'buddypress' ), 'bp-members-invitations' ); ?>
     3015                        </form>
     3016
     3017                        <form id="bp-members-invitations-form" action="<?php echo esc_url( $form_url );?>" method="post">
     3018                                <?php $bp_members_invitations_list_table->display(); ?>
     3019                        </form>
     3020                </div>
     3021        <?php
     3022        }
     3023
     3024        /**
     3025         * This is the confirmation screen for actions.
     3026         *
     3027         * @since 8.0.0
     3028         *
     3029         * @param string $action Delete or resend invitation.
     3030         * @return null|false
     3031         */
     3032        public function invitations_admin_manage( $action = '' ) {
     3033                if ( ! current_user_can( $this->capability ) || empty( $action ) ) {
     3034                        die( '-1' );
     3035                }
     3036
     3037                // Get the IDs from the URL.
     3038                $ids = false;
     3039                if ( ! empty( $_POST['invite_ids'] ) ) {
     3040                        $ids = wp_parse_id_list( $_POST['invite_ids'] );
     3041                } elseif ( ! empty( $_GET['invite_id'] ) ) {
     3042                        $ids = absint( $_GET['invite_id'] );
     3043                }
     3044
     3045
     3046                if ( empty( $ids ) ) {
     3047                        return false;
     3048                }
     3049
     3050                // Check invite IDs and set up strings.
     3051                switch ( $action ) {
     3052                        case 'delete' :
     3053                                // Query for matching invites, and filter out bad IDs.
     3054                                $args = array(
     3055                                        'id'          => $ids,
     3056                                        'invite_sent' => 'all',
     3057                                        'accepted'    => 'all',
     3058                                );
     3059                                $invites    = bp_members_invitations_get_invites( $args );
     3060                                $invite_ids = wp_list_pluck( $invites, 'id' );
     3061
     3062                                $header_text = __( 'Delete Invitations', 'buddypress' );
     3063                                if ( 0 === count( $invite_ids ) ) {
     3064                                        $helper_text = __( 'No invites were found, nothing to delete!', 'buddypress' );
     3065                                } else {
     3066                                        $helper_text = _n( 'You are about to delete the following invitation:', 'You are about to delete the following invitations:', count( $invite_ids ), 'buddypress' );
     3067                                }
     3068                                break;
     3069
     3070                        case 'resend' :
     3071                                /**
     3072                                 * Query for matching invites, and filter out bad IDs
     3073                                 * or those that have already been accepted.
     3074                                 */
     3075                                $args = array(
     3076                                        'id'          => $ids,
     3077                                        'invite_sent' => 'all',
     3078                                        'accepted'    => 'pending',
     3079                                );
     3080                                $invites    = bp_members_invitations_get_invites( $args );
     3081                                $invite_ids = wp_list_pluck( $invites, 'id' );
     3082
     3083                                $header_text = __( 'Resend Invitation Emails', 'buddypress' );
     3084                                if ( 0 === count( $invite_ids ) ) {
     3085                                        $helper_text = __( 'No pending invites were found, nothing to resend!', 'buddypress' );
     3086                                } else {
     3087                                        $helper_text = _n( 'You are about to resend an invitation email to the following address:', 'You are about to resend invitation emails to the following addresses:', count( $invite_ids ), 'buddypress' );
     3088                                }
     3089                                break;
     3090                }
     3091
     3092                // These arguments are added to all URLs.
     3093                $url_args = array( 'page' => 'bp-members-invitations' );
     3094
     3095                // These arguments are only added when performing an action.
     3096                $action_args = array(
     3097                        'action'     => 'do_' . $action,
     3098                        'invite_ids' => implode( ',', $invite_ids )
     3099                );
     3100
     3101                if ( is_network_admin() ) {
     3102                        $base_url = network_admin_url( 'admin.php' );
     3103                } else {
     3104                        $base_url = bp_get_admin_url( 'tools.php' );
     3105                }
     3106
     3107                $cancel_url = add_query_arg( $url_args, $base_url );
     3108                $action_url = wp_nonce_url(
     3109                        add_query_arg(
     3110                                array_merge( $url_args, $action_args ),
     3111                                $base_url
     3112                        ),
     3113                        'invitations_' . $action
     3114                );
     3115
     3116                ?>
     3117
     3118                <div class="wrap">
     3119                        <h1 class="wp-heading-inline"><?php echo esc_html( $header_text ); ?></h1>
     3120                        <hr class="wp-header-end">
     3121
     3122                        <p><?php echo esc_html( $helper_text ); ?></p>
     3123
     3124                        <?php if ( $invites ) : ?>
     3125
     3126                                <ol class="bp-invitations-list">
     3127                                        <?php foreach ( $invites as $invite ) :
     3128                                                if ( $invite->invite_sent ) {
     3129                                                        $last_notified = mysql2date( 'Y/m/d g:i:s a', $invite->date_modified );
     3130                                                } else {
     3131                                                        $last_notified = __( 'Not yet notified', 'buddypress');
     3132                                                }
     3133                                                ?>
     3134
     3135                                                <li>
     3136                                                        <strong><?php echo esc_html( $invite->invitee_email ) ?></strong>
     3137
     3138                                                        <?php if ( 'resend' === $action ) : ?>
     3139
     3140                                                                <p class="description">
     3141                                                                        <?php
     3142                                                                        /* translators: %s: notification date */
     3143                                                                        printf( esc_html__( 'Last notified: %s', 'buddypress'), $last_notified );
     3144                                                                        ?>
     3145                                                                </p>
     3146
     3147                                                        <?php endif; ?>
     3148
     3149                                                </li>
     3150
     3151                                        <?php endforeach; ?>
     3152                                </ol>
     3153
     3154                        <?php endif ; ?>
     3155
     3156                        <?php if ( 'delete' === $action ) : ?>
     3157
     3158                                <p><strong><?php esc_html_e( 'This action cannot be undone.', 'buddypress' ) ?></strong></p>
     3159
     3160                        <?php endif; ?>
     3161
     3162                        <?php if ( $invites ) : ?>
     3163
     3164                                <a class="button-primary" href="<?php echo esc_url( $action_url ); ?>" <?php disabled( ! $invites ); ?>><?php esc_html_e( 'Confirm', 'buddypress' ); ?></a>
     3165
     3166                        <?php endif; ?>
     3167
     3168                        <a class="button" href="<?php echo esc_url( $cancel_url ); ?>"><?php esc_html_e( 'Cancel', 'buddypress' ) ?></a>
     3169                </div>
     3170
     3171                <?php
     3172        }
     3173
    25693174}
    25703175endif; // 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 4db47da8d..c819eb08b 100644
    class BP_Members_Component extends BP_Component { 
    3939                        buddypress()->plugin_dir,
    4040                        array(
    4141                                'adminbar_myaccount_order' => 20,
    42                                 'search_query_arg' => 'members_search',
     42                                'search_query_arg'         => 'members_search',
     43                                'features'                 => array( 'invitations' )
    4344                        )
    4445                );
    4546        }
    class BP_Members_Component extends BP_Component { 
    6465                        'blocks',
    6566                        'widgets',
    6667                        'cache',
     68                        'invitations',
     69                        'notifications',
    6770                );
    6871
    6972                if ( bp_is_active( 'activity' ) ) {
    class BP_Members_Component extends BP_Component { 
    137140                        // Theme compatibility.
    138141                        new BP_Registration_Theme_Compat();
    139142                }
     143
     144                // Invitations.
     145                if ( is_user_logged_in() && bp_is_user_members_invitations() ) {
     146                        if ( bp_is_user_members_invitations_list() ) {
     147                                require $this->path . 'bp-members/screens/list-invites.php';
     148                        } else {
     149                                require $this->path . 'bp-members/screens/send-invites.php';
     150                        }
     151                }
    140152        }
    141153
    142154        /**
    class BP_Members_Component extends BP_Component { 
    180192                                'table_name_last_activity' => bp_core_get_table_prefix() . 'bp_activity',
    181193                                'table_name_optouts'       => bp_core_get_table_prefix() . 'bp_optouts',
    182194                                'table_name_signups'       => $wpdb->base_prefix . 'signups', // Signups is a global WordPress table.
    183                         )
     195                        ),
     196                        'notification_callback' => 'members_format_notifications',
    184197                );
    185198
    186199                parent::setup_globals( $args );
    class BP_Members_Component extends BP_Component { 
    233246                        $bp->profile->slug = 'profile';
    234247                        $bp->profile->id   = 'profile';
    235248                }
     249
     250                /** Network Invitations **************************************************
     251                 */
     252
     253                $bp->members->invitations = new stdClass;
    236254        }
    237255
    238256        /**
    class BP_Members_Component extends BP_Component { 
    468486                        }
    469487                }
    470488
    471 
    472489                parent::setup_nav( $main_nav, $sub_nav );
    473490        }
    474491
    class BP_Members_Component extends BP_Component { 
    549566                return $wp_admin_nav;
    550567        }
    551568
     569        /**
     570         * Get the members invitations admin bar navs.
     571         *
     572         * @since 8.0.0
     573         *
     574         * @param  string $admin_bar_menu_id The Admin bar menu ID to attach sub items to.
     575         * @return array                     The members invitations admin navs.
     576         */
     577        public function get_members_invitations_admin_navs( $admin_bar_menu_id = '' ) {
     578                $wp_admin_nav = array();
     579                $invite_link  = trailingslashit( bp_loggedin_user_domain() . bp_get_profile_slug() );
     580
     581                if ( ! $admin_bar_menu_id ) {
     582                        $admin_bar_menu_id = $this->id;
     583                }
     584
     585                return $wp_admin_nav;
     586        }
     587
    552588        /**
    553589         * Set up the Admin Bar.
    554590         *
  • new file src/bp-members/classes/class-bp-members-invitation-manager.php

    diff --git src/bp-members/classes/class-bp-members-invitation-manager.php src/bp-members/classes/class-bp-members-invitation-manager.php
    new file mode 100644
    index 000000000..a708ee17b
    - +  
     1<?php
     2/**
     3 * Membership invitations class.
     4 *
     5 * @package BuddyPress
     6 * @subpackage Core
     7 * @since 8.0.0
     8 */
     9
     10// Exit if accessed directly.
     11defined( 'ABSPATH' ) || exit;
     12
     13/**
     14 * Membership invitations class.
     15 *
     16 * An extension of the core Invitations class that adapts the
     17 * core logic to accommodate site membership invitation behavior.
     18 *
     19 * @since 8.0.0
     20 */
     21class BP_Members_Invitation_Manager extends BP_Invitation_Manager {
     22        /**
     23         * Construct parameters.
     24         *
     25         * @since 8.0.0
     26         *
     27         * @param array|string $args.
     28         */
     29        public function __construct( $args = '' ) {
     30                parent::__construct();
     31        }
     32
     33        /**
     34         * This is where custom actions are added to run when notifications of an
     35         * invitation or request need to be generated & sent.
     36         *
     37         * @since 8.0.0
     38         *
     39         * @param obj BP_Invitation $invitation The invitation to send.
     40         * @return bool True on success, false on failure.
     41         */
     42        public function run_send_action( BP_Invitation $invitation ) {
     43                // Notify site admins of the pending request
     44                if ( 'request' === $invitation->type ) {
     45                        // Coming soon to a BuddyPress near you!
     46                        return true;
     47                // Notify the invitee of the invitation.
     48                } else {
     49                        // Stop if the invitation has already been accepted.
     50                        if ( $invitation->accepted ) {
     51                                return false;
     52                        }
     53
     54                        $inviter_ud = bp_core_get_core_userdata( $invitation->inviter_id );
     55
     56                        $invite_url = esc_url(
     57                                add_query_arg(
     58                                        array(
     59                                                'inv' => $invitation->id,
     60                                                'ih'  => bp_members_invitations_get_hash( $invitation ),
     61                                        ),
     62                                        bp_get_signup_page()
     63                                )
     64                        );
     65                        $unsubscribe_args = array(
     66                                'user_id'           => 0,
     67                                'email_address'     => $invitation->invitee_email,
     68                                'member_id'         => $invitation->inviter_id,
     69                                'notification_type' => 'bp-members-invitation',
     70                        );
     71
     72                        $args = array(
     73                                'tokens' => array(
     74                                        'inviter.name'      => bp_core_get_userlink( $invitation->inviter_id, true, false, true ),
     75                                        'inviter.url'       => bp_core_get_user_domain( $invitation->inviter_id ),
     76                                        'inviter.id'        => $invitation->inviter_id,
     77                                        'invite.accept_url' => esc_url( $invite_url ),
     78                                        'usermessage'       => wp_kses( $invitation->content, array() ),
     79                                        'unsubscribe'       => esc_url( bp_email_get_unsubscribe_link( $unsubscribe_args ) ),
     80                                ),
     81                        );
     82
     83                        return bp_send_email( 'bp-members-invitation', $invitation->invitee_email, $args );
     84                }
     85        }
     86
     87        /**
     88         * This is where custom actions are added to run when an invitation
     89         * or request is accepted.
     90         *
     91         * @since 8.0.0
     92         *
     93         * @param string $type Are we accepting an invitation or request?
     94         * @param array  $r    Parameters that describe the invitation being accepted.
     95         * @return bool True on success, false on failure.
     96         */
     97        public function run_acceptance_action( $type, $r ) {
     98                if ( ! $type || ! in_array( $type, array( 'request', 'invite' ), true ) ) {
     99                        return false;
     100                }
     101
     102                if ( 'invite' === $type ) {
     103
     104                        $invites = $this->get_invitations( $r );
     105                        if ( ! $invites ) {
     106                                return;
     107                        }
     108
     109                        foreach ( $invites as $invite ) {
     110                                // Add the accepted invitation ID to the user's meta.
     111                                $new_user = get_user_by( 'email', $invite->invitee_email );
     112                                bp_update_user_meta( $new_user->ID, 'accepted_members_invitation', $invite->id );
     113
     114                                // We will mark all invitations to this user as "accepted."
     115                                if ( ! empty( $invite->invitee_email )  ) {
     116                                        $args  = array(
     117                                                'invitee_email' => $invite->invitee_email,
     118                                                'item_id'       => get_current_network_id(),
     119                                                'type'          => 'all'
     120                                        );
     121                                        $this->mark_accepted( $args );
     122                                }
     123
     124                                /**
     125                                 * Fires after a user has accepted a site membership invite.
     126                                 *
     127                                 * @since 8.0.0
     128                                 *
     129                                 * @param BP_Invitation $invite     Invitation that was accepted.
     130                                 * @param WP_user       $new_user   ID of the user who accepted the membership invite.
     131                                 * @param int           $inviter_id ID of the user who invited this user to the site.
     132                                 */
     133                                do_action( 'community_membership_invite_accepted', $invite, $new_user, $invite->inviter_id );
     134                        }
     135                }
     136
     137                return true;
     138        }
     139
     140        /**
     141         * Should this invitation be created?
     142         *
     143         * @since 8.0.0
     144         *
     145         * @param array $args.
     146         * @return bool
     147         */
     148        public function allow_invitation( $args ) {
     149                // Does the inviter have this capability?
     150                if ( ! bp_user_can( $args['inviter_id'], 'bp_members_send_invitation' ) ) {
     151                        return false;
     152                }
     153
     154                // Is the invited user eligible to receive an invitation? Hasn't opted out?
     155                if ( ! bp_user_can( 0, 'bp_members_receive_invitation', $args ) ) {
     156                        return false;
     157                }
     158
     159                return true;
     160        }
     161
     162        /**
     163         * Should this request be created?
     164         *
     165         * @since 8.0.0
     166         *
     167         * @param array $args.
     168         * @return bool.
     169         */
     170        public function allow_request( $args ) {
     171                // Does the requester have this capability?
     172                if ( ! bp_user_can( 0, 'bp_network_request_membership', $args ) ) {
     173                        return false;
     174                }
     175
     176                return true;
     177        }
     178}
  • new file src/bp-members/classes/class-bp-members-invitations-list-table.php

    diff --git src/bp-members/classes/class-bp-members-invitations-list-table.php src/bp-members/classes/class-bp-members-invitations-list-table.php
    new file mode 100644
    index 000000000..db5468ee4
    - +  
     1<?php
     2/**
     3 * BuddyPress Membership Invitation 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 Invitations admin page.
     15 *
     16 * @since 8.0.0
     17 */
     18class BP_Members_Invitations_List_Table extends WP_Users_List_Table {
     19
     20        /**
     21         * The type of view currently being displayed.
     22         *
     23         * E.g. "All", "Pending", "Sent", "Unsent"...
     24         *
     25         * @since 8.0.0
     26         * @var string
     27         */
     28        public $active_filters = array();
     29
     30        /**
     31         * Invitation counts.
     32         *
     33         * @since 8.0.0
     34         * @var int
     35         */
     36        public $total_items = 0;
     37
     38        /**
     39         * Constructor.
     40         *
     41         * @since 8.0.0
     42         */
     43        public function __construct() {
     44                // Define singular and plural labels, as well as whether we support AJAX.
     45                parent::__construct( array(
     46                        'ajax'     => false,
     47                        'plural'   => 'invitations',
     48                        'singular' => 'invitation',
     49                        'screen'   => get_current_screen()->id,
     50                ) );
     51        }
     52
     53        /**
     54         * Set up items for display in the list table.
     55         *
     56         * Handles filtering of data, sorting, pagination, and any other data
     57         * manipulation required prior to rendering.
     58         *
     59         * @since 8.0.0
     60         */
     61        public function prepare_items() {
     62                global $usersearch;
     63
     64                $search   = isset( $_REQUEST['s'] ) ? $_REQUEST['s'] : '';
     65                $per_page = $this->get_items_per_page( str_replace( '-', '_', "{$this->screen->id}_per_page" ) );
     66                $paged    = $this->get_pagenum();
     67
     68                $args = array(
     69                        'invite_sent'       => 'all',
     70                        'accepted'          => 'all',
     71                        'search_terms'      => $search,
     72                        'order_by'          => 'date_modified',
     73                        'sort_order'        => 'DESC',
     74                        'page'              => $paged,
     75                        'per_page'          => $per_page,
     76                );
     77
     78                if ( isset( $_REQUEST['accepted'] ) && in_array( $_REQUEST['accepted'], array( 'pending', 'accepted' ), true ) ) {
     79                        $args['accepted']       = $_REQUEST['accepted'];
     80                        $this->active_filters[] = $_REQUEST['accepted'];
     81                }
     82                if ( isset( $_REQUEST['sent'] ) && in_array( $_REQUEST['sent'], array( 'draft', 'sent' ), true ) ) {
     83                        $args['invite_sent']    = $_REQUEST['sent'];
     84                        $this->active_filters[] = $_REQUEST['sent'];
     85                }
     86
     87                if ( isset( $_REQUEST['orderby'] ) ) {
     88                        $args['order_by'] = $_REQUEST['orderby'];
     89                }
     90
     91                if ( isset( $_REQUEST['order'] ) ) {
     92                        $args['sort_order'] = $_REQUEST['order'];
     93                }
     94
     95                $invites_class     = new BP_Members_Invitation_Manager();
     96                $this->items       = $invites_class->get_invitations( $args );
     97                $this->total_items = $invites_class->get_invitations_total_count( $args );
     98
     99                $this->set_pagination_args( array(
     100                        'total_items' => $this->total_items,
     101                        'per_page'    => $per_page,
     102                ) );
     103        }
     104
     105        /**
     106         * Get the list of views available on this table (e.g. "all", "public").
     107         *
     108         * @since 8.0.0
     109         */
     110        public function views() {
     111                $tools_url = bp_get_admin_url( 'tools.php' );
     112
     113                if ( is_network_admin() ) {
     114                        $tools_url = network_admin_url( 'admin.php' );
     115                }
     116
     117                $url_base = add_query_arg(
     118                        array(
     119                                'page' => 'bp-members-invitations',
     120                        ),
     121                        $tools_url
     122                );
     123                ?>
     124
     125                <h2 class="screen-reader-text"><?php
     126                        /* translators: accessibility text */
     127                        esc_html_e( 'Filter invitations list', 'buddypress' );
     128                ?></h2>
     129                <ul class="subsubsub">
     130                        <li class="all">
     131                                <a href="<?php echo esc_url( $url_base ); ?>" class="<?php if ( empty( $this->active_filters ) ) echo 'current'; ?>">
     132                                        <?php esc_html_e( 'All', 'buddypress' ); ?>
     133                                </a> |
     134                        </li>
     135                        <li class="pending">
     136                                <a href="<?php echo esc_url( add_query_arg( 'accepted', 'pending', $url_base ) ); ?>" class="<?php if ( in_array( 'pending', $this->active_filters, true ) ) echo 'current'; ?>">
     137                                        <?php esc_html_e( 'Pending', 'buddypress' ); ?>
     138                                </a> |
     139                        </li>
     140                        <li class="accepted">
     141                                <a href="<?php echo esc_url( add_query_arg( 'accepted', 'accepted', $url_base ) ); ?>" class="<?php if ( in_array( 'accepted', $this->active_filters, true ) ) echo 'current'; ?>">
     142                                        <?php esc_html_e( 'Accepted', 'buddypress' ); ?>
     143                                </a> |
     144                        </li>
     145                        <li class="draft">
     146                                <a href="<?php echo esc_url( add_query_arg( 'sent', 'draft', $url_base ) ); ?>" class="<?php if ( in_array( 'draft', $this->active_filters, true ) ) echo 'current'; ?>">
     147                                        <?php esc_html_e( 'Draft (Unsent)', 'buddypress' ); ?>
     148                                </a> |
     149                        </li>
     150                        <li class="sent">
     151                                <a href="<?php echo esc_url( add_query_arg( 'sent', 'sent', $url_base ) ); ?>" class="<?php if ( in_array( 'sent', $this->active_filters, true ) ) echo 'current'; ?>">
     152                                        <?php esc_html_e( 'Sent', 'buddypress' ); ?>
     153                                </a>
     154                        </li>
     155
     156                        <?php
     157
     158                        /**
     159                         * Fires inside listing of views so plugins can add their own.
     160                         *
     161                         * @since 8.0.0
     162                         *
     163                         * @param string $url_base       Current URL base for view.
     164                         * @param array  $active_filters Current filters being requested.
     165                         */
     166                        do_action( 'bp_members_invitations_list_table_get_views', $url_base, $this->active_filters ); ?>
     167                </ul>
     168        <?php
     169        }
     170
     171        /**
     172         * Get rid of the extra nav.
     173         *
     174         * WP_Users_List_Table will add an extra nav to change user's role.
     175         * As we're dealing with invitations, we don't need this.
     176         *
     177         * @since 8.0.0
     178         *
     179         * @param array $which Current table nav item.
     180         */
     181        public function extra_tablenav( $which ) {
     182                return;
     183        }
     184
     185        /**
     186         * Specific signups columns.
     187         *
     188         * @since 8.0.0
     189         *
     190         * @return array
     191         */
     192        public function get_columns() {
     193
     194                /**
     195                 * Filters the single site Members signup columns.
     196                 *
     197                 * @since 8.0.0
     198                 *
     199                 * @param array $value Array of columns to display.
     200                 */
     201                return apply_filters( 'bp_members_invitations_list_columns', array(
     202                        'cb'                       => '<input type="checkbox" />',
     203                        'invitee_email'            => __( 'Invitee', 'buddypress' ),
     204                        'username'                 => __( 'Inviter', 'buddypress' ),
     205                        'inviter_registered_date'  => __( 'Inviter Registered', 'buddypress' ),
     206                        'invitation_date_modified' => __( 'Date Modified', 'buddypress' ),
     207                        'invitation_sent'          => __( 'Email Sent', 'buddypress' ),
     208                        'invitation_accepted'      => __( 'Accepted', 'buddypress' )
     209                ) );
     210        }
     211
     212        /**
     213         * Specific bulk actions for signups.
     214         *
     215         * @since 8.0.0
     216         */
     217        public function get_bulk_actions() {
     218                $actions = array(
     219                        'resend' => _x( 'Resend Email', 'Pending invitation action', 'buddypress' ),
     220                );
     221
     222                if ( current_user_can( 'delete_users' ) ) {
     223                        $actions['delete'] = _x( 'Delete', 'Pending invitation action', 'buddypress' );
     224                }
     225
     226                return $actions;
     227        }
     228
     229        /**
     230         * The text shown when no items are found.
     231         *
     232         * Nice job, clean sheet!
     233         *
     234         * @since 8.0.0
     235         */
     236        public function no_items() {
     237
     238                if ( bp_get_members_invitations_allowed() ) {
     239                        esc_html_e( 'No invitations found.', 'buddypress' );
     240                } else {
     241                        $link = sprintf( '<a href="%1$s">%2$s</a>', esc_url( bp_get_admin_url( add_query_arg( array( 'page' => 'bp-settings' ), 'admin.php' ) ) ), esc_html__( 'Edit settings', 'buddypress' ) );
     242
     243                        /* translators: %s: url to site settings */
     244                        printf( __( 'Invitations are not allowed. %s', 'buddypress' ), $link );
     245                }
     246
     247        }
     248
     249        /**
     250         * The columns invitations can be reordered by.
     251         *
     252         * @since 8.0.0
     253         */
     254        public function get_sortable_columns() {
     255                return array(
     256                        'invitee_email'            => 'invitee_email',
     257                        'username'                 => 'inviter_id',
     258                        'invitation_date_modified' => 'date_modified',
     259                        'invitation_sent'          => 'invite_sent',
     260                        'invitation_accepted'      => 'accepted',
     261                );
     262        }
     263
     264        /**
     265         * Display invitation rows.
     266         *
     267         * @since 8.0.0
     268         */
     269        public function display_rows() {
     270                $style = '';
     271                foreach ( $this->items as $invite ) {
     272                        $style = ( ' class="alternate"' == $style ) ? '' : ' class="alternate"';
     273                        echo "\n\t" . $this->single_row( $invite, $style );
     274                }
     275        }
     276
     277        /**
     278         * Display an invitation row.
     279         *
     280         * @since 8.0.0
     281         *
     282         * @see WP_List_Table::single_row() for explanation of params.
     283         *
     284         * @param BP_Invitation $invite   BP_Invitation object.
     285         * @param string        $style    Styles for the row.
     286         * @param string        $role     Role to be assigned to user.
     287         * @param int           $numposts Number of posts.
     288         * @return void
     289         */
     290        public function single_row( $invite = null, $style = '', $role = '', $numposts = 0 ) {
     291                echo '<tr' . $style . ' id="invitation-' . esc_attr( $invite->id ) . '">';
     292                echo $this->single_row_columns( $invite );
     293                echo '</tr>';
     294        }
     295
     296        /**
     297         * Markup for the checkbox used to select items for bulk actions.
     298         *
     299         * @since 8.0.0
     300         *
     301         * @param BP_Invitation $invite BP_Invitation object.
     302         */
     303        public function column_cb( $invite = null ) {
     304        ?>
     305                <label class="screen-reader-text" for="invitation_<?php echo intval( $invite->id ); ?>"><?php
     306                        /* translators: accessibility text */
     307                        printf( esc_html__( 'Select invitation: %s', 'buddypress' ), $invite->id );
     308                ?></label>
     309                <input type="checkbox" id="invitation_<?php echo intval( $invite->id ) ?>" name="invite_ids[]" value="<?php echo esc_attr( $invite->id ) ?>" />
     310                <?php
     311        }
     312
     313        /**
     314         * Markup for the checkbox used to select items for bulk actions.
     315         *
     316         * @since 8.0.0
     317         *
     318         * @param BP_Invitation $invite BP_Invitation object.
     319         */
     320        public function column_invitee_email( $invite = null ) {
     321                echo esc_html( $invite->invitee_email );
     322
     323                $actions = array();
     324                $tools_url = bp_get_admin_url( 'tools.php' );
     325
     326                if ( is_network_admin() ) {
     327                        $tools_url = network_admin_url( 'admin.php' );
     328                }
     329
     330                // Resend action only if pending
     331                if ( ! $invite->accepted ) {
     332                        // Resend invitation email link.
     333                        $email_link = add_query_arg(
     334                                array(
     335                                        'page'      => 'bp-members-invitations',
     336                                        'invite_id' => $invite->id,
     337                                        'action'    => 'resend',
     338                                ),
     339                                $tools_url
     340                        );
     341
     342                        if ( ! $invite->invite_sent ) {
     343                                $resend_label = __( 'Send', 'buddypress' );
     344                        } else {
     345                                $resend_label = __( 'Resend', 'buddypress' );
     346                        }
     347
     348                        $actions['resend'] = sprintf( '<a href="%1$s">%2$s</a>', esc_url( $email_link ), esc_html( $resend_label ) );
     349                }
     350
     351                // Delete link. Could be cleanup or revoking the invitation.
     352                $delete_link = add_query_arg(
     353                        array(
     354                                'page'      => 'bp-members-invitations',
     355                                'invite_id' => $invite->id,
     356                                'action'    => 'delete',
     357                        ),
     358                        $tools_url
     359                );
     360
     361                // Two cases: unsent and accepted (cleanup), and pending (cancels invite).
     362                if ( ! $invite->invite_sent || $invite->accepted ) {
     363                        $actions['delete'] = sprintf( '<a href="%1$s" class="delete">%2$s</a>', esc_url( $delete_link ), esc_html__( 'Delete', 'buddypress' ) );
     364                } else {
     365                        $actions['delete'] = sprintf( '<a href="%1$s" class="delete">%2$s</a>', esc_url( $delete_link ), esc_html__( 'Cancel', 'buddypress' ) );
     366                }
     367
     368                /**
     369                 * Filters the row actions for each invitation in list.
     370                 *
     371                 * @since 8.0.0
     372                 *
     373                 * @param array  $actions Array of actions and corresponding links.
     374                 * @param object $invite  The BP_Invitation.
     375                 */
     376                $actions = apply_filters( 'bp_members_invitations_management_row_actions', $actions, $invite );
     377
     378                echo $this->row_actions( $actions );
     379        }
     380
     381        /**
     382         * Display invited user's email address.
     383         *
     384         * @since 8.0.0
     385         *
     386         * @param BP_Invitation $invite BP_Invitation object.
     387         */
     388        public function column_email( $invite = null ) {
     389                printf( '<a href="mailto:%1$s">%2$s</a>', esc_attr( $invite->user_email ), esc_html( $invite->user_email ) );
     390        }
     391
     392        /**
     393         * The inviter.
     394         *
     395         * @since 8.0.0
     396         *
     397         * @param BP_Invitation $invite BP_Invitation object.
     398         */
     399        public function column_username( $invite = null ) {
     400                $avatar  = get_avatar( $invite->inviter_id, 32 );
     401                $inviter = get_user_by( 'id', $invite->inviter_id );
     402                if ( ! $inviter ) {
     403                        return;
     404                }
     405
     406                $user_link = bp_core_get_user_domain( $invite->inviter_id );
     407
     408                printf( '%1$s <strong><a href="%2$s" class="edit">%3$s</a></strong><br/>', $avatar, esc_url( $user_link ), esc_html( $inviter->user_login ) );
     409        }
     410
     411        /**
     412         * Display invitation date.
     413         *
     414         * @since 8.0.0
     415         *
     416         * @param BP_Invitation $invite BP_Invitation object.
     417         */
     418        public function column_inviter_registered_date( $invite = null ) {
     419                $inviter = get_user_by( 'id', $invite->inviter_id );
     420                if ( ! $inviter ) {
     421                        return;
     422                }
     423                echo esc_html( $inviter->user_registered );
     424        }
     425
     426        /**
     427         * Display invitation date.
     428         *
     429         * @since 8.0.0
     430         *
     431         * @param BP_Invitation $invite BP_Invitation object.
     432         */
     433        public function column_invitation_date_modified( $invite = null ) {
     434                echo esc_html( $invite->date_modified );
     435        }
     436
     437        /**
     438         * Display invitation date.
     439         *
     440         * @since 8.0.0
     441         *
     442         * @param BP_Invitation $invite BP_Invitation object.
     443         */
     444        public function column_invitation_sent( $invite = null ) {
     445                if ( $invite->invite_sent) {
     446                        esc_html_e( 'Yes', 'buddypress' );
     447                } else {
     448                        esc_html_e( 'No', 'buddypress' );
     449                }
     450        }
     451
     452        /**
     453         * Display invitation acceptance status.
     454         *
     455         * @since 8.0.0
     456         *
     457         * @param BP_Invitation $invite BP_Invitation object.
     458         */
     459        public function column_invitation_accepted( $invite = null ) {
     460                if ( $invite->accepted ) {
     461                        esc_html_e( 'Yes', 'buddypress' );
     462                } else {
     463                        esc_html_e( 'No', 'buddypress' );
     464                }
     465        }
     466
     467        /**
     468         * Allow plugins to add their custom column.
     469         *
     470         * @since 8.0.0
     471         *
     472         * @param BP_Invitation $invite      BP_Invitation object.
     473         * @param string        $column_name The column name.
     474         * @return string
     475         */
     476        function column_default( $invite = null, $column_name = '' ) {
     477
     478                /**
     479                 * Filters the single site custom columns for plugins.
     480                 *
     481                 * @since 8.0.0
     482                 *
     483                 * @param string $column_name The column name.
     484                 * @param object $invite      The BP_Invitation object..
     485                 */
     486                return apply_filters( 'bp_members_invitations_management_custom_column', '', $column_name, $invite );
     487        }
     488}
  • new file src/bp-members/classes/class-bp-members-invitations-template.php

    diff --git src/bp-members/classes/class-bp-members-invitations-template.php src/bp-members/classes/class-bp-members-invitations-template.php
    new file mode 100644
    index 000000000..7f1bd91af
    - +  
     1<?php
     2/**
     3 * BuddyPress Members Invitation Template Loop Class.
     4 *
     5 * @package BuddyPress
     6 * @subpackage TonificationsTemplate
     7 * @since 8.0.0
     8 */
     9
     10// Exit if accessed directly.
     11defined( 'ABSPATH' ) || exit;
     12
     13/**
     14 * The main membership invitations template loop class.
     15 *
     16 * Responsible for loading a group of membership invitations into a loop for display.
     17 *
     18 * @since 8.0.0
     19 */
     20class BP_Members_Invitations_Template {
     21
     22        /**
     23         * The loop iterator.
     24         *
     25         * @since 8.0.0
     26         * @var int
     27         */
     28        public $current_invitation = -1;
     29
     30        /**
     31         * The number of invitations returned by the paged query.
     32         *
     33         * @since 8.0.0
     34         * @var int
     35         */
     36        public $current_invitation_count;
     37
     38        /**
     39         * Total number of invitations matching the query.
     40         *
     41         * @since 8.0.0
     42         * @var int
     43         */
     44        public $total_invitation_count;
     45
     46        /**
     47         * Array of network invitations located by the query.
     48         *
     49         * @since 8.0.0
     50         * @var array
     51         */
     52        public $invitations;
     53
     54        /**
     55         * The invitation object currently being iterated on.
     56         *
     57         * @since 8.0.0
     58         * @var object
     59         */
     60        public $invitation;
     61
     62        /**
     63         * A flag for whether the loop is currently being iterated.
     64         *
     65         * @since 8.0.0
     66         * @var bool
     67         */
     68        public $in_the_loop;
     69
     70        /**
     71         * The ID of the user to whom the displayed invitations were sent.
     72         *
     73         * @since 8.0.0
     74         * @var int
     75         */
     76        public $user_id;
     77
     78        /**
     79         * The ID of the user to whom the displayed invitations belong.
     80         *
     81         * @since 8.0.0
     82         * @var int
     83         */
     84        public $inviter_id;
     85
     86        /**
     87         * The page number being requested.
     88         *
     89         * @since 8.0.0
     90         * @var int
     91         */
     92        public $pag_page;
     93
     94        /**
     95         * The $_GET argument used in URLs for determining pagination.
     96         *
     97         * @since 8.0.0
     98         * @var int
     99         */
     100        public $pag_arg;
     101
     102        /**
     103         * The number of items to display per page of results.
     104         *
     105         * @since 8.0.0
     106         * @var int
     107         */
     108        public $pag_num;
     109
     110        /**
     111         * An HTML string containing pagination links.
     112         *
     113         * @since 8.0.0
     114         * @var string
     115         */
     116        public $pag_links;
     117
     118        /**
     119         * A string to match against.
     120         *
     121         * @since 8.0.0
     122         * @var string
     123         */
     124        public $search_terms;
     125
     126        /**
     127         * A database column to order the results by.
     128         *
     129         * @since 8.0.0
     130         * @var string
     131         */
     132        public $order_by;
     133
     134        /**
     135         * The direction to sort the results (ASC or DESC).
     136         *
     137         * @since 8.0.0
     138         * @var string
     139         */
     140        public $sort_order;
     141
     142        /**
     143         * Array of variables used in this invitation query.
     144         *
     145         * @since 8.0.0
     146         * @var array
     147         */
     148        public $query_vars;
     149
     150        /**
     151         * Constructor method.
     152         *
     153         * @see bp_has_members_invitations() For information on the array format.
     154         *
     155         * @since 8.0.0
     156         *
     157         * @param array $args {
     158         *     An array of arguments. See {@link bp_has_members_invitations()}
     159         *     for more details.
     160         * }
     161         */
     162        public function __construct( $args = array() ) {
     163
     164                // Parse arguments.
     165                $r = wp_parse_args( $args, array(
     166                        'id'                => false,
     167                        'user_id'           => false,
     168                        'inviter_id'        => false,
     169                        'invitee_email'     => false,
     170                        'item_id'           => false,
     171                        'type'              => 'invite',
     172                        'invite_sent'       => 'all',
     173                        'accepted'          => 'all',
     174                        'search_terms'      => '',
     175                        'order_by'          => 'date_modified',
     176                        'sort_order'        => 'DESC',
     177                        'page'              => 1,
     178                        'per_page'          => 25,
     179                        'fields'            => 'all',
     180                        'page_arg'          => 'ipage',
     181                ) );
     182
     183                // Sort order direction.
     184                $orders = array( 'ASC', 'DESC' );
     185                if ( ! empty( $_GET['sort_order'] ) && in_array( $_GET['sort_order'], $orders ) ) {
     186                        $r['sort_order'] = $_GET['sort_order'];
     187                } else {
     188                        $r['sort_order'] = in_array( $r['sort_order'], $orders ) ? $r['sort_order'] : 'DESC';
     189                }
     190
     191                // Setup variables.
     192                $this->pag_arg      = sanitize_key( $r['page_arg'] );
     193                $this->pag_page     = bp_sanitize_pagination_arg( $this->pag_arg, $r['page']     );
     194                $this->pag_num      = bp_sanitize_pagination_arg( 'num',          $r['per_page'] );
     195                $this->user_id      = $r['user_id'];
     196                $this->search_terms = $r['search_terms'];
     197                $this->order_by     = $r['order_by'];
     198                $this->sort_order   = $r['sort_order'];
     199                $this->query_vars   = array(
     200                        'id'                => $r['id'],
     201                        'user_id'           => $r['user_id'],
     202                        'inviter_id'        => $r['inviter_id'],
     203                        'invitee_email'     => $r['invitee_email'],
     204                        'item_id'           => $r['item_id'],
     205                        'type'              => $r['type'],
     206                        'invite_sent'       => $r['invite_sent'],
     207                        'accepted'          => $r['accepted'],
     208                        'search_terms'      => $this->search_terms,
     209                        'order_by'          => $this->order_by,
     210                        'sort_order'        => $this->sort_order,
     211                        'page'              => $this->pag_page,
     212                        'per_page'          => $this->pag_num,
     213                );
     214
     215                // Setup the invitations to loop through.
     216                $invites_class = new BP_Members_Invitation_Manager();
     217
     218                $this->invitations              = $invites_class->get_invitations( $this->query_vars );
     219                $this->current_invitation_count = count( $this->invitations );
     220                $this->total_invitation_count   = $invites_class->get_invitations_total_count( $this->query_vars );
     221
     222                if ( (int) $this->total_invitation_count && (int) $this->pag_num ) {
     223                        $add_args = array(
     224                                'sort_order' => $this->sort_order,
     225                        );
     226
     227                        $this->pag_links = paginate_links( array(
     228                                'base'      => add_query_arg( $this->pag_arg, '%#%' ),
     229                                'format'    => '',
     230                                'total'     => ceil( (int) $this->total_invitation_count / (int) $this->pag_num ),
     231                                'current'   => $this->pag_page,
     232                                'prev_text' => _x( '&larr;', 'Network invitation pagination previous text', 'buddypress' ),
     233                                'next_text' => _x( '&rarr;', 'Network invitation pagination next text',     'buddypress' ),
     234                                'mid_size'  => 1,
     235                                'add_args'  => $add_args,
     236                        ) );
     237                }
     238        }
     239
     240        /**
     241         * Whether there are invitations available in the loop.
     242         *
     243         * @since 8.0.0
     244         *
     245         * @see bp_has_members_invitations()
     246         *
     247         * @return bool True if there are items in the loop, otherwise false.
     248         */
     249        public function has_invitations() {
     250                if ( $this->current_invitation_count ) {
     251                        return true;
     252                }
     253
     254                return false;
     255        }
     256
     257        /**
     258         * Set up the next invitation and iterate index.
     259         *
     260         * @since 8.0.0
     261         *
     262         * @return object The next invitation to iterate over.
     263         */
     264        public function next_invitation() {
     265
     266                $this->current_invitation++;
     267
     268                $this->invitation = $this->invitations[ $this->current_invitation ];
     269
     270                return $this->invitation;
     271        }
     272
     273        /**
     274         * Rewind the blogs and reset blog index.
     275         *
     276         * @since 8.0.0
     277         */
     278        public function rewind_invitations() {
     279
     280                $this->current_invitation = -1;
     281
     282                if ( $this->current_invitation_count > 0 ) {
     283                        $this->invitation = $this->invitations[0];
     284                }
     285        }
     286
     287        /**
     288         * Whether there are invitations left in the loop to iterate over.
     289         *
     290         * This method is used by {@link bp_members_invitations()} as part of the
     291         * while loop that controls iteration inside the invitations loop, eg:
     292         *     while ( bp_members_invitations() ) { ...
     293         *
     294         * @since 8.0.0
     295         *
     296         * @see bp_members_invitations()
     297         *
     298         * @return bool True if there are more invitations to show,
     299         *              otherwise false.
     300         */
     301        public function invitations() {
     302
     303                if ( $this->current_invitation + 1 < $this->current_invitation_count ) {
     304                        return true;
     305
     306                } elseif ( $this->current_invitation + 1 === $this->current_invitation_count ) {
     307
     308                        /**
     309                         * Fires right before the rewinding of invitation posts.
     310                         *
     311                         * @since 8.0.0
     312                         */
     313                        do_action( 'members_invitations_loop_end' );
     314
     315                        $this->rewind_invitations();
     316                }
     317
     318                $this->in_the_loop = false;
     319                return false;
     320        }
     321
     322        /**
     323         * Set up the current invitation inside the loop.
     324         *
     325         * Used by {@link bp_the_invitation()} to set up the current
     326         * invitation data while looping, so that template tags used during
     327         * that iteration make reference to the current invitation.
     328         *
     329         * @since 8.0.0
     330         *
     331         * @see bp_the_invitation()
     332         */
     333        public function the_invitation() {
     334                $this->in_the_loop = true;
     335                $this->invitation  = $this->next_invitation();
     336
     337                // Loop has just started.
     338                if ( 0 === $this->current_invitation ) {
     339
     340                        /**
     341                         * Fires if the current invitation item is the first in the invitation loop.
     342                         *
     343                         * @since 8.0.0
     344                         */
     345                        do_action( 'members_invitations_loop_start' );
     346                }
     347        }
     348}
  • new file src/bp-members/screens/list-invites.php

    diff --git src/bp-members/screens/list-invites.php src/bp-members/screens/list-invites.php
    new file mode 100644
    index 000000000..600cee733
    - +  
     1<?php
     2/**
     3 * Members: Sent Invitations Status
     4 *
     5 * @package BuddyPress
     6 * @subpackage MembersScreens
     7 * @since 8.0.0
     8 */
     9
     10/**
     11 * Catch and process the Send Invites page.
     12 *
     13 * @since 8.0.0
     14 */
     15function members_screen_list_sent_invites() {
     16
     17        /**
     18         * Fires before the loading of template for the send membership invitations page.
     19         *
     20         * @since 8.0.0
     21         */
     22        do_action( 'members_screen_list_sent_invites' );
     23
     24        /**
     25         * Filters the template used to display the send membership invitations page.
     26         *
     27         * @since 8.0.0
     28         *
     29         * @param string $template Path to the send membership invitations template to load.
     30         */
     31        bp_core_load_template( apply_filters( 'members_template_list_sent_invites', 'members/single/invitations' ) );
     32}
     33
     34/**
     35 * Handle canceling or resending single invitations.
     36 *
     37 * @since 8.0.0
     38 *
     39 * @return bool
     40 */
     41function bp_members_invitations_action_handling() {
     42
     43        // Bail if not the read screen.
     44        if ( ! bp_is_user_members_invitations_list() ) {
     45                return false;
     46        }
     47
     48        // Get the action.
     49        $action = ! empty( $_GET['action']        ) ? $_GET['action']        : '';
     50        $nonce  = ! empty( $_GET['_wpnonce']      ) ? $_GET['_wpnonce']      : '';
     51        $id     = ! empty( $_GET['invitation_id'] ) ? $_GET['invitation_id'] : '';
     52
     53        // Bail if no action or no ID.
     54        if ( empty( $action ) || empty( $id ) ) {
     55                return false;
     56        }
     57
     58        if ( 'cancel' === $action ) {
     59                // Check the nonce and delete the invitation.
     60                if ( bp_verify_nonce_request( 'bp_members_invitations_cancel_' . $id ) && bp_members_invitations_delete_by_id( $id ) ) {
     61                        bp_core_add_message( __( 'Invitation successfully canceled.', 'buddypress' ) );
     62                } else {
     63                        bp_core_add_message( __( 'There was a problem canceling that invitation.', 'buddypress' ), 'error' );
     64                }
     65        } else if ( 'resend' === $action ) {
     66                // Check the nonce and resend the invitation.
     67                if ( bp_verify_nonce_request( 'bp_network_invitation_resend_' . $id ) && bp_members_invitation_resend_by_id( $id ) ) {
     68                        bp_core_add_message( __( 'Invitation successfully resent.', 'buddypress' ) );
     69                } else {
     70                        bp_core_add_message( __( 'There was a problem resending that invitation.', 'buddypress' ), 'error' );
     71                }
     72        } else {
     73                return false;
     74        }
     75
     76        // Redirect.
     77        $user_id = bp_displayed_user_id();
     78        bp_core_redirect( bp_get_members_invitations_list_invites_permalink( $user_id ) );
     79}
     80add_action( 'bp_actions', 'bp_members_invitations_action_handling' );
  • src/bp-members/screens/register.php

    diff --git src/bp-members/screens/register.php src/bp-members/screens/register.php
    index 54b6facd5..77929c442 100644
    function bp_core_screen_signup() { 
    4242
    4343        $bp->signup->step = 'request-details';
    4444
    45         if ( !bp_get_signup_allowed() ) {
    46                 $bp->signup->step = 'registration-disabled';
     45        // Could the user be accepting an invitation?
     46        $active_invite = false;
     47        if ( bp_get_members_invitations_allowed() ) {
     48                // Check to see if there's a valid invitation.
     49                $maybe_invite = bp_get_members_invitation_from_request();
     50                if ( $maybe_invite->id && $maybe_invite->invitee_email ) {
     51                        // Check if this user is already a member.
     52                        $args = array(
     53                                'invitee_email' => $maybe_invite->invitee_email,
     54                                'accepted'      => 'accepted',
     55                                'fields'        => 'ids',
     56                        );
     57                        $accepted_invites = bp_members_invitations_get_invites( $args );
     58                        if ( ! $accepted_invites ) {
     59                                $active_invite = true;
     60                        }
     61                }
     62        }
    4763
     64        if ( ! bp_get_signup_allowed() && ! $active_invite ) {
     65                $bp->signup->step = 'registration-disabled';
    4866                // If the signup page is submitted, validate and save.
    4967        } elseif ( isset( $_POST['signup_submit'] ) && bp_verify_nonce_request( 'bp_new_signup' ) ) {
    5068
  • new file src/bp-members/screens/send-invites.php

    diff --git src/bp-members/screens/send-invites.php src/bp-members/screens/send-invites.php
    new file mode 100644
    index 000000000..f2af326e2
    - +  
     1<?php
     2/**
     3 * Members: Send Invitations
     4 *
     5 * @package BuddyPress
     6 * @subpackage MembersScreens
     7 * @since 8.0.0
     8 */
     9
     10/**
     11 * Catch and process the Send Invites page.
     12 *
     13 * @since 8.0.0
     14 */
     15function members_screen_send_invites() {
     16
     17        /**
     18         * Fires before the loading of template for the send membership invitations page.
     19         *
     20         * @since 8.0.0
     21         */
     22        do_action( 'members_screen_send_invites' );
     23
     24        /**
     25         * Filters the template used to display the send membership invitations page.
     26         *
     27         * @since 8.0.0
     28         *
     29         * @param string $template Path to the send membership invitations template to load.
     30         */
     31        bp_core_load_template( apply_filters( 'members_template_send_invites', 'members/single/invitations' ) );
     32}
     33
     34/**
     35 * Handle sending invitations.
     36 *
     37 * @since 8.0.0
     38 *
     39 * @return bool
     40 */
     41function bp_network_invitations_catch_send_action() {
     42
     43        // Bail if not the read screen.
     44        if ( ! bp_is_user_members_invitations_send_screen() ) {
     45                return false;
     46        }
     47
     48        // Get the action.
     49        $action  = ! empty( $_REQUEST['action']          ) ? $_REQUEST['action']          : '';
     50        $nonce   = ! empty( $_REQUEST['_wpnonce']        ) ? $_REQUEST['_wpnonce']        : '';
     51        $email   = ! empty( $_REQUEST['invitee_email']   ) ? $_REQUEST['invitee_email']   : '';
     52        $message = ! empty( $_REQUEST['invite_message']  ) ? $_REQUEST['invite_message']  : '';
     53
     54        // Bail if missing required info.
     55        if ( ( 'send-invite' !== $action ) ) {
     56                return false;
     57        }
     58
     59        $invite_args = array(
     60                'invitee_email' => $email,
     61                'inviter_id'    => bp_displayed_user_id(),
     62                'content'       => $message,
     63                'send_invite'   => 1
     64        );
     65
     66        // Check the nonce and delete the invitation.
     67        if ( bp_verify_nonce_request( 'bp_members_invitation_send_' . bp_displayed_user_id() ) && bp_members_invitations_invite_user( $invite_args ) ) {
     68                bp_core_add_message( __( 'Invitation successfully sent!', 'buddypress' )          );
     69        } else {
     70                bp_core_add_message( __( 'There was a problem sending that invitation. The user could already be a member of the site or have chosen not to receive invitations from this site.', 'buddypress' ), 'error' );
     71        }
     72
     73        // Redirect.
     74        $user_id = bp_displayed_user_id();
     75        bp_core_redirect( bp_get_members_invitations_send_invites_permalink( $user_id ) );
     76}
     77add_action( 'bp_actions', 'bp_network_invitations_catch_send_action' );
  • src/bp-templates/bp-legacy/buddypress-functions.php

    diff --git src/bp-templates/bp-legacy/buddypress-functions.php src/bp-templates/bp-legacy/buddypress-functions.php
    index f4a3e1016..4da3d1ae9 100644
    function bp_legacy_theme_group_manage_members_add_search() { 
    20142014                <?php
    20152015        endif;
    20162016}
     2017
     2018/**
     2019 * Modify welcome message in Legacy template pack when
     2020 * community invitations are enabled.
     2021 *
     2022 * @since 8.0.0
     2023 *
     2024 * @return string $message The message text.
     2025 */
     2026function bp_members_invitations_add_legacy_welcome_message() {
     2027        $message = bp_members_invitations_get_registration_welcome_message();
     2028        if ( $message ) {
     2029                echo '<p>' . $message . '</p>';
     2030        }
     2031}
     2032add_action( 'bp_before_register_page', 'bp_members_invitations_add_legacy_welcome_message' );
     2033
     2034
     2035/**
     2036 * Modify "registration disabled" message in Legacy template pack when
     2037 * community invitations are enabled.
     2038 *
     2039 * @since 8.0.0
     2040 *
     2041 * @return string $message The message text.
     2042 */
     2043function bp_members_invitations_add_legacy_registration_disabled_message() {
     2044        $message = bp_members_invitations_get_modified_registration_disabled_message();
     2045        if ( $message ) {
     2046                echo '<p>' . esc_html( $message ) . '</p>';
     2047        }
     2048}
     2049add_action( 'bp_after_registration_disabled', 'bp_members_invitations_add_legacy_registration_disabled_message' );
  • src/bp-templates/bp-legacy/buddypress/members/single/home.php

    diff --git src/bp-templates/bp-legacy/buddypress/members/single/home.php src/bp-templates/bp-legacy/buddypress/members/single/home.php
    index f56af6e9c..bed9da1bf 100644
     
    8989                elseif ( bp_is_user_notifications() ) :
    9090                        bp_get_template_part( 'members/single/notifications' );
    9191
     92                elseif ( bp_is_user_members_invitations() ) :
     93                        bp_get_template_part( 'members/single/invitations' );
     94
    9295                elseif ( bp_is_user_settings() ) :
    9396                        bp_get_template_part( 'members/single/settings' );
    9497
  • new file src/bp-templates/bp-legacy/buddypress/members/single/invitations.php

    diff --git src/bp-templates/bp-legacy/buddypress/members/single/invitations.php src/bp-templates/bp-legacy/buddypress/members/single/invitations.php
    new file mode 100644
    index 000000000..876a2e314
    - +  
     1<?php
     2/**
     3 * BuddyPress - membership invitations
     4 *
     5 * @package BuddyPress
     6 * @subpackage bp-legacy
     7 * @version 8.0.0
     8 */
     9
     10?>
     11
     12<div class="item-list-tabs no-ajax" id="subnav" aria-label="<?php esc_attr_e( 'Member secondary navigation', 'buddypress' ); ?>" role="navigation">
     13        <ul>
     14                <?php bp_get_options_nav(); ?>
     15        </ul>
     16</div>
     17
     18<?php
     19switch ( bp_current_action() ) :
     20
     21        case 'send-invites' :
     22                bp_get_template_part( 'members/single/invitations/send-invites' );
     23                break;
     24
     25        case 'list-invites' :
     26        default :
     27                bp_get_template_part( 'members/single/invitations/list-invites' );
     28                break;
     29
     30endswitch;
     31
  • new file src/bp-templates/bp-legacy/buddypress/members/single/invitations/invitations-loop.php

    diff --git src/bp-templates/bp-legacy/buddypress/members/single/invitations/invitations-loop.php src/bp-templates/bp-legacy/buddypress/members/single/invitations/invitations-loop.php
    new file mode 100644
    index 000000000..f73857cc8
    - +  
     1<?php
     2/**
     3 * BuddyPress - Membership Invitations Loop
     4 *
     5 * @package BuddyPress
     6 * @subpackage bp-legacy
     7 * @version 8.0.0
     8 */
     9
     10?>
     11<form action="" method="post" id="invitations-bulk-management">
     12        <table class="invitations">
     13                <thead>
     14                        <tr>
     15                                <th class="icon"></th>
     16                                <th class="bulk-select-all"><input id="select-all-invitations" type="checkbox">
     17                                        <label class="bp-screen-reader-text" for="select-all-invitations">
     18                                                <?php
     19                                                /* translators: accessibility text */
     20                                                esc_html_e( 'Select all', 'buddypress' );
     21                                                ?>
     22                                        </label>
     23                                </th>
     24                                <th class="title"><?php esc_html_e( 'Invitee', 'buddypress' ); ?></th>
     25                                <th class="content"><?php esc_html_e( 'Message', 'buddypress' ); ?></th>
     26                                <th class="sent"><?php esc_html_e( 'Sent', 'buddypress' ); ?></th>
     27                                <th class="accepted"><?php esc_html_e( 'Accepted', 'buddypress' ); ?></th>
     28                                <th class="date"><?php esc_html_e( 'Date Modified', 'buddypress' ); ?></th>
     29                                <th class="actions"><?php esc_html_e( 'Actions', 'buddypress' ); ?></th>
     30                        </tr>
     31                </thead>
     32
     33                <tbody>
     34
     35                        <?php while ( bp_the_members_invitations() ) : bp_the_members_invitation(); ?>
     36
     37                                <tr>
     38                                        <td></td>
     39                                        <td class="bulk-select-check">
     40                                                <label for="<?php bp_the_members_invitation_property( 'id', 'attribute' ); ?>">
     41                                                        <input id="<?php bp_the_members_invitation_property( 'id', 'attribute' ); ?>" type="checkbox" name="network_invitations[]" value="<?php bp_the_members_invitation_property( 'id', 'attribute' ); ?>" class="invitation-check">
     42                                                        <span class="bp-screen-reader-text">
     43                                                                <?php
     44                                                                        /* translators: accessibility text */
     45                                                                        esc_html_e( 'Select this invitation', 'buddypress' );
     46                                                                ?>
     47                                                        </span>
     48                                                </label>
     49                                        </td>
     50                                        <td class="invitation-invitee"><?php bp_the_members_invitation_property( 'invitee_email' );  ?></td>
     51                                        <td class="invitation-content"><?php bp_the_members_invitation_property( 'content' );  ?></td>
     52                                        <td class="invitation-sent"><?php bp_the_members_invitation_property( 'invite_sent' );  ?></td>
     53                                        <td class="invitation-accepted"><?php bp_the_members_invitation_property( 'accepted' );  ?></td>
     54                                        <td class="invitation-date-modified"><?php bp_the_members_invitation_property( 'date_modified' );   ?></td>
     55                                        <td class="invitation-actions"><?php bp_the_members_invitation_action_links(); ?></td>
     56                                </tr>
     57
     58                        <?php endwhile; ?>
     59
     60                </tbody>
     61        </table>
     62
     63        <div class="invitations-options-nav">
     64                <?php // @TODO //bp_invitations_bulk_management_dropdown(); ?>
     65        </div><!-- .invitations-options-nav -->
     66
     67        <?php wp_nonce_field( 'invitations_bulk_nonce', 'invitations_bulk_nonce' ); ?>
     68</form>
  • new file src/bp-templates/bp-legacy/buddypress/members/single/invitations/list-invites.php

    diff --git src/bp-templates/bp-legacy/buddypress/members/single/invitations/list-invites.php src/bp-templates/bp-legacy/buddypress/members/single/invitations/list-invites.php
    new file mode 100644
    index 000000000..8e299f1d6
    - +  
     1<?php
     2/**
     3 * BuddyPress - Sent Membership Invitations
     4 *
     5 * @package BuddyPress
     6 * @subpackage bp-legacy
     7 * @version 8.0.0
     8 */
     9?>
     10
     11<?php if ( bp_has_members_invitations() ) : ?>
     12
     13        <h2 class="bp-screen-reader-text">
     14                <?php
     15                /* translators: accessibility text */
     16                esc_html_e( 'Invitations', 'buddypress' );
     17                ?>
     18        </h2>
     19
     20        <div id="pag-top" class="pagination no-ajax">
     21                <div class="pag-count" id="invitations-count-top">
     22                        <?php bp_members_invitations_pagination_count(); ?>
     23                </div>
     24
     25                <div class="pagination-links" id="invitations-pag-top">
     26                        <?php bp_members_invitations_pagination_links(); ?>
     27                </div>
     28        </div>
     29
     30        <?php bp_get_template_part( 'members/single/invitations/invitations-loop' ); ?>
     31
     32        <div id="pag-bottom" class="pagination no-ajax">
     33                <div class="pag-count" id="invitations-count-bottom">
     34                        <?php bp_members_invitations_pagination_count(); ?>
     35                </div>
     36
     37                <div class="pagination-links" id="invitations-pag-bottom">
     38                        <?php bp_members_invitations_pagination_links(); ?>
     39                </div>
     40        </div>
     41
     42<?php else : ?>
     43
     44        <p><?php esc_html_e( 'There are no invitations to display.', 'buddypress' ); ?></p>
     45
     46<?php endif;
  • new file src/bp-templates/bp-legacy/buddypress/members/single/invitations/send-invites.php

    diff --git src/bp-templates/bp-legacy/buddypress/members/single/invitations/send-invites.php src/bp-templates/bp-legacy/buddypress/members/single/invitations/send-invites.php
    new file mode 100644
    index 000000000..6eeb3b62f
    - +  
     1<?php
     2/**
     3 * BuddyPress - Sent Membership Invitations
     4 *
     5 * @package BuddyPress
     6 * @subpackage bp-legacy
     7 * @version 8.0.0
     8 */
     9?>
     10<h2 class="bp-screen-reader-text">
     11        <?php
     12        /* translators: accessibility text */
     13        esc_html_e( 'Send Invitations', 'buddypress' );
     14        ?>
     15</h2>
     16
     17<form class="standard-form members-invitation-form" id="members-invitation-form" method="post">
     18        <p class="description"><?php esc_html_e( 'Fill out the form below to invite a new user to join this site. Upon submission of the form, an email will be sent to the invitee containing a link to accept your invitation. You may also add a custom message to the email.', 'buddypress' ); ?></p>
     19
     20        <label for="bp_members_invitation_invitee_email"><?php esc_html_e( 'Email address of new user', 'buddypress' ); ?></label>
     21        <input id="bp_members_invitation_invitee_email" type="email" name="invitee_email" required="required">
     22
     23        <label for="bp_members_invitation_message"><?php esc_html_e( 'Add a personalized message to the invitation (optional)', 'buddypress' ); ?></label>
     24        <textarea id="bp_members_invitation_message" name="invite_message"></textarea>
     25
     26        <input type="hidden" name="action" value="send-invite">
     27
     28        <?php wp_nonce_field( 'bp_members_invitation_send_' . bp_displayed_user_id() ) ?>
     29        <p>
     30                <input id="submit" type="submit" name="submit" class="submit" value="<?php esc_attr_e( 'Send Invitation', 'buddypress' ) ?>" />
     31        </p>
     32</form>
  • src/bp-templates/bp-nouveau/buddypress-functions.php

    diff --git src/bp-templates/bp-nouveau/buddypress-functions.php src/bp-templates/bp-nouveau/buddypress-functions.php
    index 892bf2e33..f93134f74 100644
    class BP_Nouveau extends BP_Theme_Compat { 
    195195                // Set the BP Uri for the Ajax customizer preview.
    196196                add_filter( 'bp_uri', array( $this, 'customizer_set_uri' ), 10, 1 );
    197197
     198                // Modify "registration disabled" and welcome message if invitations are enabled.
     199                add_action( 'bp_nouveau_feedback_messages', array( $this, 'filter_registration_messages' ), 99 );
     200
    198201                /** Override **********************************************************/
    199202
    200203                /**
    class BP_Nouveau extends BP_Theme_Compat { 
    676679
    677680                return $path;
    678681        }
     682        /**
     683         * Modify "registration disabled" message in Nouveau template pack.
     684         * Modify welcome message in Nouveau template pack.
     685         *
     686         * @since 8.0.0
     687         *
     688         * @param array $messages The list of feedback messages.
     689         *
     690         * @return array $messages
     691         */
     692        function filter_registration_messages( $messages ) {
     693                // Change the "registration is disabled" message.
     694                $disallowed_message = bp_members_invitations_get_modified_registration_disabled_message();
     695                if ( $disallowed_message ) {
     696                        $messages['registration-disabled']['message'] = $disallowed_message;
     697                }
     698                // Add information about invitations to the welcome block.
     699                $welcome_message = bp_members_invitations_get_registration_welcome_message();
     700                if ( $welcome_message ) {
     701                        $messages['request-details']['message'] = $welcome_message . $messages['request-details']['message'];
     702                }
     703                return $messages;
     704        }
    679705}
    680706
    681707/**
  • new file src/bp-templates/bp-nouveau/buddypress/members/single/invitations.php

    diff --git src/bp-templates/bp-nouveau/buddypress/members/single/invitations.php src/bp-templates/bp-nouveau/buddypress/members/single/invitations.php
    new file mode 100644
    index 000000000..1fd36bab1
    - +  
     1<?php
     2/**
     3 * BuddyPress - Membership invitations
     4 *
     5 * @since 8.0.0
     6 * @version 8.0.0
     7 */
     8?>
     9
     10<nav class="<?php bp_nouveau_single_item_subnav_classes(); ?>" id="subnav" role="navigation" aria-label="<?php esc_attr_e( 'Groups menu', 'buddypress' ); ?>">
     11        <ul class="subnav">
     12                <?php bp_get_template_part( 'members/single/parts/item-subnav' ); ?>
     13        </ul>
     14</nav><!-- .bp-navs -->
     15
     16<?php
     17switch ( bp_current_action() ) :
     18
     19        case 'send-invites' :
     20                bp_get_template_part( 'members/single/invitations/send-invites' );
     21                break;
     22
     23        case 'list-invites' :
     24        default :
     25                bp_get_template_part( 'members/single/invitations/list-invites' );
     26                break;
     27
     28endswitch;
     29
  • new file src/bp-templates/bp-nouveau/buddypress/members/single/invitations/invitations-loop.php

    diff --git src/bp-templates/bp-nouveau/buddypress/members/single/invitations/invitations-loop.php src/bp-templates/bp-nouveau/buddypress/members/single/invitations/invitations-loop.php
    new file mode 100644
    index 000000000..f4aa643ee
    - +  
     1<?php
     2/**
     3 * BuddyPress - Membership Invitations Loop
     4 *
     5 * @since 8.0.0
     6 * @version 8.0.0
     7 */
     8?>
     9<form action="" method="post" id="invitations-bulk-management" class="standard-form">
     10        <table class="invitations">
     11                <thead>
     12                        <tr>
     13                                <th class="bulk-select-all"><input id="select-all-invitations" type="checkbox">
     14                                        <label class="bp-screen-reader-text" for="select-all-invitations">
     15                                                <?php
     16                                                /* translators: accessibility text */
     17                                                esc_html_e( 'Select all', 'buddypress' );
     18                                                ?>
     19                                        </label>
     20                                </th>
     21                                <th class="title"><?php esc_html_e( 'Invitee', 'buddypress' ); ?></th>
     22                                <th class="content"><?php esc_html_e( 'Message', 'buddypress' ); ?></th>
     23                                <th class="sent"><?php esc_html_e( 'Sent', 'buddypress' ); ?></th>
     24                                <th class="accepted"><?php esc_html_e( 'Accepted', 'buddypress' ); ?></th>
     25                                <th class="date"><?php esc_html_e( 'Date Modified', 'buddypress' ); ?></th>
     26                                <th class="actions"><?php esc_html_e( 'Actions', 'buddypress' ); ?></th>
     27                        </tr>
     28                </thead>
     29
     30                <tbody>
     31
     32                        <?php while ( bp_the_members_invitations() ) : bp_the_members_invitation(); ?>
     33
     34                                <tr>
     35                                        <td class="bulk-select-check">
     36                                                <label for="<?php bp_the_members_invitation_property( 'id', 'attribute' ); ?>">
     37                                                        <input id="<?php bp_the_members_invitation_property( 'id', 'attribute' ); ?>" type="checkbox" name="network_invitations[]" value="<?php bp_the_members_invitation_property( 'id', 'attribute' ); ?>" class="invitation-check">
     38                                                        <span class="bp-screen-reader-text">
     39                                                                <?php
     40                                                                        /* translators: accessibility text */
     41                                                                        esc_html_e( 'Select this invitation', 'buddypress' );
     42                                                                ?>
     43                                                        </span>
     44                                                </label>
     45                                        </td>
     46                                        <td class="invitation-invitee"><?php bp_the_members_invitation_property( 'invitee_email' ); ?></td>
     47                                        <td class="invitation-content"><?php bp_the_members_invitation_property( 'content' ); ?></td>
     48                                        <td class="invitation-sent"><?php bp_the_members_invitation_property( 'invite_sent' ); ?></td>
     49                                        <td class="invitation-accepted"><?php bp_the_members_invitation_property( 'accepted' ); ?></td>
     50                                        <td class="invitation-date-modified"><?php bp_the_members_invitation_property( 'date_modified' ); ?></td>
     51                                        <td class="invitation-actions"><?php bp_the_members_invitation_action_links(); ?></td>
     52                                </tr>
     53
     54                        <?php endwhile; ?>
     55
     56                </tbody>
     57        </table>
     58
     59        <div class="invitations-options-nav">
     60                <?php bp_nouveau_invitations_bulk_management_dropdown(); ?>
     61        </div><!-- .invitations-options-nav -->
     62
     63        <?php wp_nonce_field( 'invitations_bulk_nonce', 'invitations_bulk_nonce' ); ?>
     64</form>
  • new file src/bp-templates/bp-nouveau/buddypress/members/single/invitations/list-invites.php

    diff --git src/bp-templates/bp-nouveau/buddypress/members/single/invitations/list-invites.php src/bp-templates/bp-nouveau/buddypress/members/single/invitations/list-invites.php
    new file mode 100644
    index 000000000..a83054098
    - +  
     1<?php
     2/**
     3 * BuddyPress - Pending Membership Invitations
     4 *
     5 * @since 8.0.0
     6 * @version 8.0.0
     7 */
     8?>
     9
     10<?php if ( bp_has_members_invitations() ) : ?>
     11
     12        <h2 class="bp-screen-reader-text">
     13                <?php
     14                /* translators: accessibility text */
     15                esc_html_e( 'Invitations', 'buddypress' );
     16                ?>
     17        </h2>
     18
     19        <div id="pag-top" class="bp-pagination no-ajax">
     20                <div class="pag-count" id="invitations-count-top">
     21                        <?php bp_members_invitations_pagination_count(); ?>
     22                </div>
     23
     24                <div class="pagination-links" id="invitations-pag-top">
     25                        <?php bp_members_invitations_pagination_links(); ?>
     26                </div>
     27        </div>
     28
     29        <?php bp_get_template_part( 'members/single/invitations/invitations-loop' ); ?>
     30
     31        <div id="pag-bottom" class="bp-pagination no-ajax">
     32                <div class="pag-count" id="invitations-count-bottom">
     33                        <?php bp_members_invitations_pagination_count(); ?>
     34                </div>
     35
     36                <div class="pagination-links" id="invitations-pag-bottom">
     37                        <?php bp_members_invitations_pagination_links(); ?>
     38                </div>
     39        </div>
     40
     41<?php else : ?>
     42
     43        <?php bp_nouveau_user_feedback( 'member-invites-none' ); ?>
     44
     45<?php endif;
  • new file src/bp-templates/bp-nouveau/buddypress/members/single/invitations/send-invites.php

    diff --git src/bp-templates/bp-nouveau/buddypress/members/single/invitations/send-invites.php src/bp-templates/bp-nouveau/buddypress/members/single/invitations/send-invites.php
    new file mode 100644
    index 000000000..077c1fa24
    - +  
     1<?php
     2/**
     3 * BuddyPress - Send a Membership Invitation.
     4 *
     5 * @since 8.0.0
     6 * @version 8.0.0
     7 */
     8?>
     9<h2 class="bp-screen-reader-text">
     10        <?php
     11        /* translators: accessibility text */
     12        esc_html_e( 'Send Invitation', 'buddypress' );
     13        ?>
     14</h2>
     15
     16<p class="bp-feedback info">
     17        <span class="bp-icon" aria-hidden="true"></span>
     18        <span class="bp-help-text">
     19                <?php esc_html_e( 'Fill out the form below to invite a new user to join this site. Upon submission of the form, an email will be sent to the invitee containing a link to accept your invitation. You may also add a custom message to the email.', 'buddypress' ); ?>
     20        </span>
     21</p>
     22
     23<form class="standard-form network-invitation-form" id="network-invitation-form" method="post">
     24        <label for="bp_members_invitation_invitee_email">
     25                <?php esc_html_e( 'Email', 'buddypress' ); ?>
     26                <span class="bp-required-field-label"><?php esc_html_e( '(required)', 'buddypress' ); ?></span>
     27        </label>
     28        <input id="bp_members_invitation_invitee_email" type="email" name="invitee_email" required="required">
     29
     30        <label for="bp_members_invitation_message">
     31                <?php esc_html_e( 'Add a personalized message to the invitation (optional)', 'buddypress' ); ?>
     32        </label>
     33        <textarea id="bp_members_invitation_message" name="invite_message"></textarea>
     34
     35        <input type="hidden" name="action" value="send-invite">
     36
     37        <?php bp_nouveau_submit_button( 'member-send-invite' ); ?>
     38</form>
  • src/bp-templates/bp-nouveau/common-styles/_bp_filters.scss

    diff --git src/bp-templates/bp-nouveau/common-styles/_bp_filters.scss src/bp-templates/bp-nouveau/common-styles/_bp_filters.scss
    index 8ff5970f1..1317aee14 100644
     
    181181
    182182        } // close .subnav-filters
    183183
    184         .notifications-options-nav {
    185 
    186                 input#notification-bulk-manage {
    187                         border: 0;
    188                         border-radius: 0;
    189                         line-height: 1.6;
    190                 }
     184        .notifications-options-nav input#notification-bulk-manage,
     185        .invitations-options-nav input#invitation-bulk-manage {
     186                border: 0;
     187                border-radius: 0;
     188                line-height: 1.6;
    191189        }
    192190
    193191        .group-subnav-filters {
  • src/bp-templates/bp-nouveau/common-styles/_bp_forms.scss

    diff --git src/bp-templates/bp-nouveau/common-styles/_bp_forms.scss src/bp-templates/bp-nouveau/common-styles/_bp_forms.scss
    index b39d8a03c..9453a4452 100644
     
    2424                opacity: 0.4;
    2525        }
    2626
    27         #notification-bulk-manage[disabled] {
     27        #notification-bulk-manage[disabled],
     28        #invitation-bulk-manage[disabled] {
    2829                display: none;
    2930        }
    3031
    body.no-js { 
    399400
    400401        @include medium-small-up() {
    401402
    402                 .notifications-options-nav {
    403 
    404                         .select-wrap {
    405                                 float: left;
    406                         }
     403                .notifications-options-nav .select-wrap,
     404                .invitations-options-nav .select-wrap {
     405                        float: left;
    407406                }
    408407        }
    409408}
  • src/bp-templates/bp-nouveau/common-styles/_bp_generic_and_typography.scss

    diff --git src/bp-templates/bp-nouveau/common-styles/_bp_generic_and_typography.scss src/bp-templates/bp-nouveau/common-styles/_bp_generic_and_typography.scss
    index 65cabf5e7..2d2799368 100644
    body.buddypress { 
    255255                }
    256256        }
    257257
    258         #notification-select {
     258        #notification-select,
     259        #invitation-select {
    259260
    260261                @include responsive-font(14);
    261262        }
  • src/bp-templates/bp-nouveau/css/buddypress-rtl.css

    diff --git src/bp-templates/bp-nouveau/css/buddypress-rtl.css src/bp-templates/bp-nouveau/css/buddypress-rtl.css
    index 35b21b66d..20f7c1a32 100644
    body.buddypress article.page > .entry-header:not(.alignwide):not(.alignfull) .en 
    255255        }
    256256}
    257257
    258 .buddypress-wrap #notification-select {
     258.buddypress-wrap #notification-select,
     259.buddypress-wrap #invitation-select {
    259260        font-size: 12px;
    260261}
    261262
    262263@media screen and (min-width: 46.8em) {
    263         .buddypress-wrap #notification-select {
     264        .buddypress-wrap #notification-select,
     265        .buddypress-wrap #invitation-select {
    264266                font-size: 14px;
    265267        }
    266268}
    body.buddypress article.page > .entry-header:not(.alignwide):not(.alignfull) .en 
    794796        }
    795797}
    796798
    797 .buddypress-wrap .notifications-options-nav input#notification-bulk-manage {
     799.buddypress-wrap .notifications-options-nav input#notification-bulk-manage,
     800.buddypress-wrap .invitations-options-nav input#invitation-bulk-manage {
    798801        border: 0;
    799802        border-radius: 0;
    800803        line-height: 1.6;
    body.buddypress.settings.data #buddypress.buddypress-wrap .item-body p a { 
    34303433        opacity: 0.4;
    34313434}
    34323435
    3433 .buddypress-wrap #notification-bulk-manage[disabled] {
     3436.buddypress-wrap #notification-bulk-manage[disabled],
     3437.buddypress-wrap #invitation-bulk-manage[disabled] {
    34343438        display: none;
    34353439}
    34363440
    body.no-js .buddypress #messages-bulk-management #select-all-messages { 
    37273731}
    37283732
    37293733@media screen and (min-width: 32em) {
    3730         .buddypress-wrap .notifications-options-nav .select-wrap {
     3734        .buddypress-wrap .notifications-options-nav .select-wrap,
     3735        .buddypress-wrap .invitations-options-nav .select-wrap {
    37313736                float: right;
    37323737        }
    37333738}
  • src/bp-templates/bp-nouveau/css/buddypress.css

    diff --git src/bp-templates/bp-nouveau/css/buddypress.css src/bp-templates/bp-nouveau/css/buddypress.css
    index 2196e878f..7cc80ca7f 100644
    body.buddypress article.page > .entry-header:not(.alignwide):not(.alignfull) .en 
    255255        }
    256256}
    257257
    258 .buddypress-wrap #notification-select {
     258.buddypress-wrap #notification-select,
     259.buddypress-wrap #invitation-select {
    259260        font-size: 12px;
    260261}
    261262
    262263@media screen and (min-width: 46.8em) {
    263         .buddypress-wrap #notification-select {
     264        .buddypress-wrap #notification-select,
     265        .buddypress-wrap #invitation-select {
    264266                font-size: 14px;
    265267        }
    266268}
    body.buddypress article.page > .entry-header:not(.alignwide):not(.alignfull) .en 
    794796        }
    795797}
    796798
    797 .buddypress-wrap .notifications-options-nav input#notification-bulk-manage {
     799.buddypress-wrap .notifications-options-nav input#notification-bulk-manage,
     800.buddypress-wrap .invitations-options-nav input#invitation-bulk-manage {
    798801        border: 0;
    799802        border-radius: 0;
    800803        line-height: 1.6;
    body.buddypress.settings.data #buddypress.buddypress-wrap .item-body p a { 
    34303433        opacity: 0.4;
    34313434}
    34323435
    3433 .buddypress-wrap #notification-bulk-manage[disabled] {
     3436.buddypress-wrap #notification-bulk-manage[disabled],
     3437.buddypress-wrap #invitation-bulk-manage[disabled] {
    34343438        display: none;
    34353439}
    34363440
    body.no-js .buddypress #messages-bulk-management #select-all-messages { 
    37273731}
    37283732
    37293733@media screen and (min-width: 32em) {
    3730         .buddypress-wrap .notifications-options-nav .select-wrap {
     3734        .buddypress-wrap .notifications-options-nav .select-wrap,
     3735        .buddypress-wrap .invitations-options-nav .select-wrap {
    37313736                float: left;
    37323737        }
    37333738}
  • src/bp-templates/bp-nouveau/css/twentytwentyone-rtl.css

    diff --git src/bp-templates/bp-nouveau/css/twentytwentyone-rtl.css src/bp-templates/bp-nouveau/css/twentytwentyone-rtl.css
    index 453826cd5..d3d05a346 100644
    Hello, this is the BP Nouveau's Twenty Twenty-One companion stylesheet. 
    154154        padding-right: 0;
    155155}
    156156
    157 #buddypress.twentytwentyone .notifications-options-nav {
     157#buddypress.twentytwentyone .notifications-options-nav,
     158#buddypress.twentytwentyone .invitations-options-nav {
    158159        margin-top: 1em;
    159160}
    160161
    161 #buddypress.twentytwentyone .notifications-options-nav input#notification-bulk-manage {
     162#buddypress.twentytwentyone .notifications-options-nav input#notification-bulk-manage,
     163#buddypress.twentytwentyone .invitations-options-nav input#invitation-bulk-manage {
    162164        line-height: 1.2;
    163165}
    164166
  • src/bp-templates/bp-nouveau/css/twentytwentyone.css

    diff --git src/bp-templates/bp-nouveau/css/twentytwentyone.css src/bp-templates/bp-nouveau/css/twentytwentyone.css
    index ecfaf8d9e..a8837db02 100644
    Hello, this is the BP Nouveau's Twenty Twenty-One companion stylesheet. 
    154154        padding-left: 0;
    155155}
    156156
    157 #buddypress.twentytwentyone .notifications-options-nav {
     157#buddypress.twentytwentyone .notifications-options-nav,
     158#buddypress.twentytwentyone .invitations-options-nav {
    158159        margin-top: 1em;
    159160}
    160161
    161 #buddypress.twentytwentyone .notifications-options-nav input#notification-bulk-manage {
     162#buddypress.twentytwentyone .notifications-options-nav input#notification-bulk-manage,
     163#buddypress.twentytwentyone .invitations-options-nav input#invitation-bulk-manage {
    162164        line-height: 1.2;
    163165}
    164166
  • src/bp-templates/bp-nouveau/includes/functions.php

    diff --git src/bp-templates/bp-nouveau/includes/functions.php src/bp-templates/bp-nouveau/includes/functions.php
    index 9ee9b52c5..1b17c6294 100644
    function bp_nouveau_theme_cover_image( $params = array() ) { 
    927927 * All user feedback messages are available here
    928928 *
    929929 * @since 3.0.0
     930 * @since 8.0.0 Adds the 'member-invites-none' feedback.
    930931 *
    931932 * @param string $feedback_id The ID of the message.
    932933 *
    function bp_nouveau_get_user_feedback( $feedback_id = '' ) { 
    939940         * Use this filter to add your custom feedback messages.
    940941         *
    941942         * @since 3.0.0
     943         * @since 8.0.0 Adds the 'member-invites-none' feedback.
    942944         *
    943945         * @param array $value The list of feedback messages.
    944946         */
    945         $feedback_messages = apply_filters( 'bp_nouveau_feedback_messages', array(
    946                 'registration-disabled' => array(
    947                         'type'    => 'info',
    948                         'message' => __( 'Member registration is currently not allowed.', 'buddypress' ),
    949                         'before'  => 'bp_before_registration_disabled',
    950                         'after'   => 'bp_after_registration_disabled'
    951                 ),
    952                 'request-details' => array(
    953                         'type'    => 'info',
    954                         'message' => __( 'Registering for this site is easy. Just fill in the fields below, and we\'ll get a new account set up for you in no time.', 'buddypress' ),
    955                         'before'  => false,
    956                         'after'   => false,
    957                 ),
    958                 'completed-confirmation' => array(
    959                         'type'    => 'info',
    960                         'message' => __( 'You have successfully created your account! Please log in using the username and password you have just created.', 'buddypress' ),
    961                         'before'  => 'bp_before_registration_confirmed',
    962                         'after'   => 'bp_after_registration_confirmed',
    963                 ),
    964                 'directory-activity-loading' => array(
    965                         'type'    => 'loading',
    966                         'message' => __( 'Loading the community updates. Please wait.', 'buddypress' ),
    967                 ),
    968                 'single-activity-loading' => array(
    969                         'type'    => 'loading',
    970                         'message' => __( 'Loading the update. Please wait.', 'buddypress' ),
    971                 ),
    972                 'activity-loop-none' => array(
    973                         'type'    => 'info',
    974                         'message' => __( 'Sorry, there was no activity found. Please try a different filter.', 'buddypress' ),
    975                 ),
    976                 'blogs-loop-none' => array(
    977                         'type'    => 'info',
    978                         'message' => __( 'Sorry, there were no sites found.', 'buddypress' ),
    979                 ),
    980                 'blogs-no-signup' => array(
    981                         'type'    => 'info',
    982                         'message' => __( 'Site registration is currently disabled.', 'buddypress' ),
    983                 ),
    984                 'directory-blogs-loading' => array(
    985                         'type'    => 'loading',
    986                         'message' => __( 'Loading the sites of the network. Please wait.', 'buddypress' ),
    987                 ),
    988                 'directory-groups-loading' => array(
    989                         'type'    => 'loading',
    990                         'message' => __( 'Loading the groups of the community. Please wait.', 'buddypress' ),
    991                 ),
    992                 'groups-loop-none' => array(
    993                         'type'    => 'info',
    994                         'message' => __( 'Sorry, there were no groups found.', 'buddypress' ),
    995                 ),
    996                 'group-activity-loading' => array(
    997                         'type'    => 'loading',
    998                         'message' => __( 'Loading the group updates. Please wait.', 'buddypress' ),
    999                 ),
    1000                 'group-members-loading' => array(
    1001                         'type'    => 'loading',
    1002                         'message' => __( 'Requesting the group members. Please wait.', 'buddypress' ),
    1003                 ),
    1004                 'group-members-none' => array(
    1005                         'type'    => 'info',
    1006                         'message' => __( 'Sorry, there were no group members found.', 'buddypress' ),
    1007                 ),
    1008                 'group-members-search-none' => array(
    1009                         'type'    => 'info',
    1010                         'message' => __( 'Sorry, there was no member of that name found in this group.', 'buddypress' ),
    1011                 ),
    1012                 'group-manage-members-none' => array(
    1013                         'type'    => 'info',
    1014                         'message' => __( 'This group has no members.', 'buddypress' ),
    1015                 ),
    1016                 'group-requests-none' => array(
    1017                         'type'    => 'info',
    1018                         'message' => __( 'There are no pending membership requests.', 'buddypress' ),
    1019                 ),
    1020                 'group-requests-loading' => array(
    1021                         'type'    => 'loading',
    1022                         'message' => __( 'Loading the members who requested to join the group. Please wait.', 'buddypress' ),
    1023                 ),
    1024                 'group-delete-warning' => array(
    1025                         'type'    => 'warning',
    1026                         'message' => __( 'WARNING: Deleting this group will completely remove ALL content associated with it. There is no way back. Please be careful with this option.', 'buddypress' ),
    1027                 ),
    1028                 'group-avatar-delete-info' => array(
    1029                         'type'    => 'info',
    1030                         'message' => __( 'If you\'d like to remove the existing group profile photo but not upload a new one, please use the delete group profile photo button.', 'buddypress' ),
    1031                 ),
    1032                 'directory-members-loading' => array(
    1033                         'type'    => 'loading',
    1034                         'message' => __( 'Loading the members of your community. Please wait.', 'buddypress' ),
    1035                 ),
    1036                 'members-loop-none' => array(
    1037                         'type'    => 'info',
    1038                         'message' => __( 'Sorry, no members were found.', 'buddypress' ),
    1039                 ),
    1040                 'member-requests-none' => array(
    1041                         'type'    => 'info',
    1042                         'message' => __( 'You have no pending friendship requests.', 'buddypress' ),
    1043                 ),
    1044                 'member-invites-none' => array(
    1045                         'type'    => 'info',
    1046                         'message' => __( 'You have no outstanding group invites.', 'buddypress' ),
    1047                 ),
    1048                 'member-notifications-none' => array(
    1049                         'type'    => 'info',
    1050                         'message' => __( 'This member has no notifications.', 'buddypress' ),
    1051                 ),
    1052                 'member-wp-profile-none' => array(
    1053                         'type'    => 'info',
    1054                         /* translators: %s: member name */
    1055                         'message' => __( '%s did not save any profile information yet.', 'buddypress' ),
    1056                 ),
    1057                 'member-delete-account' => array(
    1058                         'type'    => 'warning',
    1059                         'message' => __( 'Deleting this account will delete all of the content it has created. It will be completely unrecoverable.', 'buddypress' ),
    1060                 ),
    1061                 'member-activity-loading' => array(
    1062                         'type'    => 'loading',
    1063                         'message' => __( 'Loading the member\'s updates. Please wait.', 'buddypress' ),
    1064                 ),
    1065                 'member-blogs-loading' => array(
    1066                         'type'    => 'loading',
    1067                         'message' => __( 'Loading the member\'s blogs. Please wait.', 'buddypress' ),
    1068                 ),
    1069                 'member-friends-loading' => array(
    1070                         'type'    => 'loading',
    1071                         'message' => __( 'Loading the member\'s friends. Please wait.', 'buddypress' ),
    1072                 ),
    1073                 'member-groups-loading' => array(
    1074                         'type'    => 'loading',
    1075                         'message' => __( 'Loading the member\'s groups. Please wait.', 'buddypress' ),
    1076                 ),
    1077                 'member-notifications-loading' => array(
    1078                         'type'    => 'loading',
    1079                         'message' => __( 'Loading notifications. Please wait.', 'buddypress' ),
    1080                 ),
    1081                 'member-group-invites-all' => array(
    1082                         'type'    => 'info',
    1083                         'message' => __( 'Currently every member of the community can invite you to join their groups. If you are not comfortable with it, you can always restrict group invites to your friends only.', 'buddypress' ),
    1084                 ),
    1085                 'member-group-invites-friends-only' => array(
    1086                         'type'    => 'info',
    1087                         'message' => __( 'Currently only your friends can invite you to groups. Uncheck the box to allow any member to send invites.', 'buddypress' ),
    1088                 ),
    1089         ) );
     947        $feedback_messages = apply_filters(
     948                'bp_nouveau_feedback_messages',
     949                array(
     950                        'registration-disabled'             => array(
     951                                'type'    => 'info',
     952                                'message' => __( 'Member registration is currently not allowed.', 'buddypress' ),
     953                                'before'  => 'bp_before_registration_disabled',
     954                                'after'   => 'bp_after_registration_disabled'
     955                        ),
     956                        'request-details'                   => array(
     957                                'type'    => 'info',
     958                                'message' => __( 'Registering for this site is easy. Just fill in the fields below, and we\'ll get a new account set up for you in no time.', 'buddypress' ),
     959                                'before'  => false,
     960                                'after'   => false,
     961                        ),
     962                        'completed-confirmation'            => array(
     963                                'type'    => 'info',
     964                                'message' => __( 'You have successfully created your account! Please log in using the username and password you have just created.', 'buddypress' ),
     965                                'before'  => 'bp_before_registration_confirmed',
     966                                'after'   => 'bp_after_registration_confirmed',
     967                        ),
     968                        'directory-activity-loading'        => array(
     969                                'type'    => 'loading',
     970                                'message' => __( 'Loading the community updates. Please wait.', 'buddypress' ),
     971                        ),
     972                        'single-activity-loading'           => array(
     973                                'type'    => 'loading',
     974                                'message' => __( 'Loading the update. Please wait.', 'buddypress' ),
     975                        ),
     976                        'activity-loop-none'                => array(
     977                                'type'    => 'info',
     978                                'message' => __( 'Sorry, there was no activity found. Please try a different filter.', 'buddypress' ),
     979                        ),
     980                        'blogs-loop-none'                   => array(
     981                                'type'    => 'info',
     982                                'message' => __( 'Sorry, there were no sites found.', 'buddypress' ),
     983                        ),
     984                        'blogs-no-signup'                   => array(
     985                                'type'    => 'info',
     986                                'message' => __( 'Site registration is currently disabled.', 'buddypress' ),
     987                        ),
     988                        'directory-blogs-loading'           => array(
     989                                'type'    => 'loading',
     990                                'message' => __( 'Loading the sites of the network. Please wait.', 'buddypress' ),
     991                        ),
     992                        'directory-groups-loading'          => array(
     993                                'type'    => 'loading',
     994                                'message' => __( 'Loading the groups of the community. Please wait.', 'buddypress' ),
     995                        ),
     996                        'groups-loop-none'                  => array(
     997                                'type'    => 'info',
     998                                'message' => __( 'Sorry, there were no groups found.', 'buddypress' ),
     999                        ),
     1000                        'group-activity-loading'            => array(
     1001                                'type'    => 'loading',
     1002                                'message' => __( 'Loading the group updates. Please wait.', 'buddypress' ),
     1003                        ),
     1004                        'group-members-loading'             => array(
     1005                                'type'    => 'loading',
     1006                                'message' => __( 'Requesting the group members. Please wait.', 'buddypress' ),
     1007                        ),
     1008                        'group-members-none'                => array(
     1009                                'type'    => 'info',
     1010                                'message' => __( 'Sorry, there were no group members found.', 'buddypress' ),
     1011                        ),
     1012                        'group-members-search-none'         => array(
     1013                                'type'    => 'info',
     1014                        &n