Skip to:
Content

BuddyPress.org

Ticket #6712: 6712.04.patch

File 6712.04.patch, 18.2 KB (added by r-a-y, 3 years ago)
  • new file src/bp-notifications/bp-notifications-settings.php

    new file mode 100644
    - +  
     1<?php
     2/**
     3 * BuddyPress Notifications Settings.
     4 *
     5 * Functions and hooks related to the Settings component.
     6 *
     7 * @package BuddyPress
     8 * @since 2.7.0
     9 */
     10
     11/** REGISTRATION *********************************************************/
     12
     13/**
     14 * Register our screen notification type with the Settings component.
     15 *
     16 * @since 2.7.0
     17 */
     18function bp_notifications_register_notification_settings_type() {
     19        bp_settings_register_notification_type( 'screen', array(
     20                'label'         => esc_html__( 'Screen', 'buddypress' ),
     21                'post_variable' => 'screen'
     22        ) );
     23}
     24add_action( 'bp_setup_globals', 'bp_notifications_register_notification_settings_type' );
     25
     26/** HOOKS ****************************************************************/
     27
     28/**
     29 * Block screen notification if a user has disabled it from their settings page.
     30 *
     31 * @since 2.7.0
     32 *
     33 * @param BP_Notification_Notification $n
     34 */
     35function bp_notifications_block_screen_notifications_for_user_hook( $n ) {
     36        $disabled = bp_get_user_meta( $n->user_id, 'screen_notifications_disabled', true );
     37
     38        $component_action = $n->component_action;
     39
     40        // Edge cases - the following actions are shared.
     41        if ( 'member_promoted_to_admin' === $component_action ) {
     42                $component_action = 'member_promoted_to_mod';
     43        } elseif ( 'membership_request_accepted' === $component_action ) {
     44                $component_action = 'membership_request_rejected';
     45        }
     46
     47        // Block notification from being recorded by wiping out the component name.
     48        if ( isset( $disabled[ $component_action ] ) ) {
     49                $n->component_name = '';
     50        }
     51}
     52add_action( 'bp_notification_before_save', 'bp_notifications_block_screen_notifications_for_user_hook' );
     53
     54/** SCREEN ***************************************************************/
     55
     56/**
     57 * Revamp the "Settings > Notifications" page to use a checkbox interface.
     58 *
     59 * @since 2.7.0
     60 */
     61function bp_notifications_new_checkbox_interface() {
     62        if ( ! did_action( 'bp_before_member_settings_template' ) ) {
     63                return;
     64        }
     65
     66        add_action( 'bp_notification_settings', 'bp_notifications_new_checkbox_interface_end_buffer', 99999999 );
     67
     68        ob_start();
     69}
     70add_action( 'bp_notification_settings', 'bp_notifications_new_checkbox_interface', -99999999 );
     71
     72/**
     73 * Umm... this is madness! Proof of concept. Not commit-worthy.
     74 *
     75 * @since 2.7.0
     76 */
     77function bp_notifications_new_checkbox_interface_end_buffer() {
     78        $html = ob_get_contents();
     79        ob_end_clean();
     80
     81        $notification_types = bp_settings_get_notification_types();
     82        if ( empty( $notification_types ) ) {
     83                return;
     84        }
     85
     86        $options = array();
     87
     88        libxml_use_internal_errors( false );
     89        $dom = new DOMDocument();
     90        $dom->loadHTML( $html );
     91
     92        // Loop through each notification group.
     93        foreach ( $dom->getElementsByTagName('tbody') as $node ) {
     94                $option = '';
     95
     96                $group_title = $node->parentNode->childNodes->item(0)->childNodes->item(0)->childNodes->item(2)->nodeValue;
     97                $key         = sanitize_title( $group_title );
     98
     99                $options[ $key ] = array(
     100                        'title' => $group_title
     101                );
     102
     103                $options[ $key ][ 'options' ] = array();
     104
     105                // Loop through the <tr>'s to grab the options for each group.
     106                foreach ( $node->childNodes as $item ) {
     107                        if ( $item->childNodes->item(4) ) {
     108                                $option = $item->childNodes->item(4)->firstChild->attributes->getNamedItem( 'name' )->value;
     109                                $option = str_replace( 'notifications[', '', $option );
     110                                $option = str_replace( ']', '', $option );
     111       
     112
     113                        }
     114
     115                        // Option description.
     116                        if ( $item->childNodes->item(2) && ! empty( $option ) ) {
     117                                $options[ $key ][ 'options' ][ $option ] = $item->childNodes->item(2)->textContent;
     118                        }
     119
     120                }
     121           
     122        }
     123
     124        unset( $dom );
     125
     126        foreach( $options as $group_key => $group ) {
     127                if ( empty( $group['options'] ) ) {
     128                        continue;
     129                }
     130
     131        ?>
     132
     133        <fieldset>
     134        <legend><?php esc_html_e( $group['title'] ); ?></legend>
     135
     136                <?php foreach ( $group['options'] as $key => $desc ) : ?>
     137                        <p id="<?php esc_attr_e( $key ); ?>"><?php esc_html_e( $desc ); ?></p>
     138
     139                        <?php
     140                                foreach ( $notification_types as $ntype => $nargs ) :
     141                                        $metakey = bp_settings_get_metakey_for_notification_type( $key, $ntype );
     142                                        if ( '' === $metakey ) {
     143                                                continue;
     144                                        }
     145
     146                                        $setting = bp_settings_get_notification_user_setting( 0, $metakey, $ntype );
     147                        ?>
     148
     149                                <label for="<?php printf( '%1$s-%2$s', $ntype, $metakey ); ?>" aria-describedby="<?php esc_attr_e( $key ); ?>">
     150                                        <input name="<?php printf( '%1$s[%2$s]', bp_settings_get_notification_type( $ntype )->post_variable, esc_attr( $metakey ) ); ?>" id="<?php printf( '%1$s-%2$s', $ntype, $metakey ); ?>" value="yes" <?php checked( $setting, 'yes', true ); ?> type="checkbox">
     151                                <?php esc_html_e( bp_settings_get_notification_type( $ntype )->label ); ?></label>
     152
     153                        <?php endforeach; ?>
     154
     155                <?php endforeach; ?>
     156
     157        </fieldset>
     158
     159        <?php
     160        }
     161}
     162
     163/**
     164 * Saves custom user settings for our notification type.
     165 *
     166 * @since 2.7.0
     167 */
     168function bp_notifications_save_screen_notification_settings() {
     169        $ntype = bp_settings_get_notification_type( 'screen' );
     170        if ( false === $ntype || empty( $_POST[ $ntype->post_variable ] ) ) {
     171                return;
     172        }
     173
     174        $removed = array_diff_key( bp_notifications_get_mapped_notification_actions(), $_POST[ $ntype->post_variable ] );
     175
     176        // Edge cases - These options are shared with other email options.
     177        unset( $removed['member_promoted_to_admin'], $removed[ 'membership_request_accepted' ] );
     178
     179        // We only save disabled screen notification data.
     180        bp_update_user_meta( bp_displayed_user_id(), 'screen_notifications_disabled', array_fill_keys( array_keys( $removed ), 1 ) );
     181}
     182add_action( 'bp_core_notification_settings_after_save', 'bp_notifications_save_screen_notification_settings' );
     183
     184/** UTILITY **************************************************************/
     185
     186/**
     187 * Fetch mappped notification actions to notification meta keys.
     188 *
     189 * @since 2.7.0
     190 * @todo Would be nice to merge this with bp_email_get_type_schema() somehow...
     191 *
     192 * @return array
     193 */
     194function bp_notifications_get_mapped_notification_actions() {
     195        $actions = array(
     196                // Activity
     197                // notification action -> meta key
     198                'new_at_mention' => 'activity_new_mention',
     199                'comment_reply'  => 'activity_new_reply',
     200
     201                // Friends
     202                'friendship_requested' => 'friends_friendship_request',
     203                'friendship_accepted'  => 'friends_friendship_accepted',
     204
     205                // Groups
     206                'group_invite' => 'groups_invite',
     207                'member_promoted_to_admin' => 'groups_admin_promotion',
     208                'member_promoted_to_mod'   => 'groups_admin_promotion',
     209                'new_membership_request'   => 'groups_membership_request',
     210                'membership_request_accepted' => 'membership_request_completed',
     211                'membership_request_rejected' => 'membership_request_completed',
     212
     213                // Messages
     214                'new_message' => 'messages_new_message'
     215        );
     216
     217        return apply_filters( 'bp_notifications_get_mapped_notification_actions', $actions );
     218}
     219
     220/**
     221 * Get custom metakey for our notification type.
     222 *
     223 * @since 2.7.0
     224 *
     225 * @param string $key Current notification key.
     226 */
     227function bp_notifications_get_metakey_for_notification_type( $key ) {
     228        $key = str_replace( 'notification_', '', $key );
     229
     230        $mapped_keys = array_flip( bp_notifications_get_mapped_notification_actions() );
     231
     232        if ( isset( $mapped_keys[ $key ] ) ) {
     233                return $mapped_keys[ $key ];
     234
     235        /*
     236         * 3rd-party screen notifications need to be registered on the
     237         * 'bp_notifications_get_mapped_notification_actions' filter.
     238         */
     239        } else {
     240                return '';
     241        }
     242}
     243add_filter( 'bp_settings_get_metakey_for_screen_notification_type', 'bp_notifications_get_metakey_for_notification_type', 50 );
     244
     245/**
     246 * Get user setting for our screen notification type.
     247 *
     248 * @since 2.7.0
     249 *
     250 * @param  string $setting Current setting. Default: 'yes'.
     251 * @param  int    $user_id User ID.
     252 * @param  string $key     Meta key.
     253 * @return string
     254 */
     255function bp_notifications_filter_user_notification_setting( $setting, $user_id, $key ) {
     256        $disabled_screen_notifications = bp_get_user_meta( $user_id, 'screen_notifications_disabled', true );
     257
     258        if ( isset( $disabled_screen_notifications[ $key ] ) ) {
     259                $setting = 'no';
     260        }
     261
     262        return $setting;
     263}
     264add_filter( 'bp_settings_get_screen_notification_user_setting', 'bp_notifications_filter_user_notification_setting', 10, 3 );
     265 No newline at end of file
  • src/bp-notifications/classes/class-bp-notifications-component.php

     
    5252                        'cache',
    5353                );
    5454
     55                if ( bp_is_active( 'settings' ) ) {
     56                        $includes[] = 'settings';
     57                }
     58
    5559                if ( ! buddypress()->do_autoload ) {
    5660                        $includes[] = 'classes';
    5761                }
  • src/bp-notifications/classes/class-bp-notifications-notification.php

     
    125125                 */
    126126                do_action_ref_array( 'bp_notification_before_save', array( &$this ) );
    127127
     128                // Ensure that the 'component_name' property is always set.
     129                if ( empty( $this->component_name ) ) {
     130                        return false;
     131                }
     132
    128133                $data = array(
    129134                        'user_id'           => $this->user_id,
    130135                        'item_id'           => $this->item_id,
  • src/bp-settings/bp-settings-functions.php

     
    7777                $sanitized_settings[ $key ] = $value;
    7878        }
    7979
     80        // Handle new checkbox interface, specifically unchecked settings.
     81        $enabled_notifications = array_intersect( array_keys( $settings ), $registered_notification_settings );
     82        $removed_notifications = array_unique( array_diff( $registered_notification_settings, $enabled_notifications ) );
     83        foreach ( $removed_notifications as $key ) {
     84                $sanitized_settings[ $key ] = 'no';
     85        }
     86
    8087        return $sanitized_settings;
    8188}
    8289
     
    103110
    104111        return $key_whitelist;
    105112}
     113
     114/**
     115 * Register a notification type on the "Settings > Notifications" page.
     116 *
     117 * Used primarily to output a user's "Settings > Notifications" page, but
     118 * could be used for other things.
     119 *
     120 * You should run this function after 'bp_setup_globals'.
     121 *
     122 * @since 2.7.0
     123 *
     124 * @param string $type The notification type to register.
     125 * @param array $args {
     126 *     Array of arguments to register for the notification type.
     127 *     @type string $label         Label for the notification type.
     128 *     @type string $post_variable Unique post variable used for input name in form.
     129 * }
     130 * @return bool
     131 */
     132function bp_settings_register_notification_type( $type = '', $args = array() ) {
     133        if ( ! did_action( 'bp_setup_globals' ) ) {
     134                _doing_it_wrong( __FUNCTION__, "Function must be called after the 'bp_setup_globals' hook." );
     135                return false;
     136        }
     137
     138        if ( empty( $type ) || empty( $args ) ) {
     139                return false;
     140        }
     141
     142        if ( ! isset( buddypress()->settings->notification_type ) ) {
     143                buddypress()->settings->notification_type = array();
     144        }
     145
     146        $component = sanitize_key( $type );
     147
     148        buddypress()->settings->notification_type[$type] = (object) $args;
     149
     150        return true;
     151}
     152
     153/**
     154 * Fetch all available notification types.
     155 *
     156 * @since 2.7.0
     157 *
     158 * @return array
     159 */
     160function bp_settings_get_notification_types() {
     161        if ( ! isset( buddypress()->settings->notification_type ) ) {
     162                $types = array();
     163        }
     164
     165        // Email always exists by default.
     166        $types['email'] = (object) array(
     167                'label'         => esc_html__( 'Email', 'buddypress' ),
     168                'post_variable' => 'notifications'
     169        );
     170
     171        /**
     172         * Filters the notification types for 3rd-party notification types.
     173         */
     174        $custom_types = apply_filters( 'bp_settings_get_notification_types', buddypress()->settings->notification_type );
     175
     176        return $types + $custom_types;
     177}
     178
     179/**
     180 * Get data about a registered notification type
     181 *
     182 * @since 2.7.0
     183 *
     184 * @param  string $type Notification type.
     185 * @return object|false
     186 */
     187function bp_settings_get_notification_type( $type ) {
     188        $types = bp_settings_get_notification_types();
     189        if ( isset( $types[ $type ] ) ) {
     190                return $types[ $type ];
     191        } else {
     192                return false;
     193        }
     194}
     195
     196/**
     197 * Get the meta key used to store the user's setting for the notification type
     198 *
     199 * @since 2.7.0
     200 *
     201 * @param  string $key  Meta key to use when checking user meta.
     202 * @param  string $type Notification type to check against.
     203 * @return string
     204 */
     205function bp_settings_get_metakey_for_notification_type( $key, $type ) {
     206        // Email already works.
     207        if ( 'email' === $type ) {
     208                return $key;
     209        }
     210
     211        $unmodified_key = $key;
     212
     213        /**
     214         * Dynamic filter used to determine the meta key for the current notification type.
     215         *
     216         * @since 2.7.0
     217         *
     218         * @param string $key Meta key to use when checking user meta.
     219         */
     220        $key = apply_filters( "bp_settings_get_metakey_for_{$type}_notification_type", $key );
     221
     222        // If key didn't change, return blank string.
     223        if ( $key === bp_settings_get_metakey_for_notification_type( $unmodified_key, 'email' ) ) {
     224                return '';
     225        } else {
     226                return $key;
     227        }
     228}
     229
     230/**
     231 * Get a user's notification user setting, depending on the notification type
     232 *
     233 * @since 2.7.0
     234 *
     235 * @param  string $key  Meta key to use when checking user meta.
     236 * @param  string $type Notification type to check against.
     237 * @return string
     238 */
     239function bp_settings_get_notification_user_setting( $user_id = 0, $key = '', $type = '' ) {
     240        if ( 0 === $user_id ) {
     241                $user_id = bp_displayed_user_id();
     242        }
     243
     244        // Email is hardcoded.
     245        if ( 'email' === $type ) {
     246                $setting = bp_get_user_meta( $user_id, $key, true );
     247
     248                // No setting saved; default is 'yes'.
     249                if ( '' === $setting ) {
     250                        $setting = 'yes';
     251                }
     252
     253                return $setting;
     254        }
     255
     256        /**
     257         * Allow 3rd-party notification types to filter the notification user setting
     258         *
     259         * Filter name is dynamic, depending on the notification type.
     260         *
     261         * @since 2.7.0
     262         *
     263         * @param string $setting Defaults to 'yes'.
     264         * @param int    $user_id User ID to fetch the notification setting for.
     265         * @param string $key     Notification key.
     266         */
     267        return apply_filters( "bp_settings_get_{$type}_notification_user_setting", 'yes', $user_id, $key );
     268}
  • src/bp-settings/classes/class-bp-settings-component.php

     
    117117                        'user_has_access' => $access
    118118                );
    119119
    120                 // Add Email nav item. Formerly called 'Notifications', we
    121                 // retain the old slug and function names for backward compat.
     120                // Add Notifications nav item.
    122121                $sub_nav[] = array(
    123                         'name'            => __( 'Email', 'buddypress' ),
     122                        'name'            => __( 'Notifications', 'buddypress' ),
    124123                        'slug'            => 'notifications',
    125124                        'parent_url'      => $settings_link,
    126125                        'parent_slug'     => $slug,
     
    195194                                $wp_admin_nav[] = array(
    196195                                        'parent'   => 'my-account-' . $this->id,
    197196                                        'id'       => 'my-account-' . $this->id . '-notifications',
    198                                         'title'    => __( 'Email', 'buddypress' ),
     197                                        'title'    => __( 'Notifications', 'buddypress' ),
    199198                                        'href'     => trailingslashit( $settings_link . 'notifications' ),
    200199                                        'position' => 20
    201200                                );
  • src/bp-templates/bp-legacy/buddypress/members/single/settings/notifications.php

     
    99/** This action is documented in bp-templates/bp-legacy/buddypress/members/single/settings/profile.php */
    1010do_action( 'bp_before_member_settings_template' ); ?>
    1111
     12<h2 class="bp-screen-reader-text"><?php _e( 'Notification Settings', 'buddypress' ); ?></h2>
     13
    1214<form action="<?php echo bp_displayed_user_domain() . bp_get_settings_slug() . '/notifications'; ?>" method="post" class="standard-form" id="settings-form">
    13         <p><?php _e( 'Send an email notice when:', 'buddypress' ); ?></p>
    1415
    1516        <?php
    1617
  • src/bp-templates/bp-legacy/css/buddypress.css

     
    875875        word-wrap: normal !important;
    876876}
    877877
     878/* Settings > Notifications */
     879#settings-form.standard-form fieldset {
     880        margin-bottom: 1em;
     881}
     882
     883#settings-form.standard-form legend {
     884        margin-bottom: 15px;
     885}
     886
     887#settings-form.standard-form fieldset p {
     888        clear: both;
     889        margin-bottom: 0;
     890}
     891
     892#settings-form.standard-form label {
     893        float: left;
     894        margin-bottom: 1em;
     895        margin-right: 3em;
     896}
     897
    878898/*--------------------------------------------------------------
    8798993.6 - Ajax Loading
    880900--------------------------------------------------------------*/
  • tests/phpunit/testcases/notifications/functions.php

     
    394394        }
    395395
    396396        /**
     397         * @group notification_settings
     398         */
     399        public function test_notifications_disabled_by_user() {
     400                $u = $this->factory->user->create();
     401
     402                // Disable notifications for the 'new_at_mention' action.
     403                bp_update_user_meta( $u, 'screen_notifications_disabled', array( 'new_at_mention' => 1 ) );
     404
     405                // This should be blocked.
     406                $n1 = $this->factory->notification->create( array(
     407                        'component_name'    => 'activity',
     408                        'component_action'  => 'new_at_mention',
     409                        'item_id'           => 99,
     410                        'user_id'           => $u,
     411                ) );
     412
     413                // This should pass through.
     414                $n2 = $this->factory->notification->create( array(
     415                        'component_name'    => 'activity',
     416                        'component_action'  => 'kwyjibo',
     417                        'item_id'           => 99,
     418                        'user_id'           => $u,
     419                ) );
     420
     421                $this->assertFalse( $n1 );
     422                $this->assertNotFalse( $n2 );
     423        }
     424
     425        /**
    397426         * Used in test_notification_callback_parameter_integrity() test.
    398427         */
    399428        public function dummy_notification_callback( $action, $item_id, $secondary_item_id, $total_items, $format = 'string', $id = 0 ) {