Making WordPress.org


Ignore:
Timestamp:
06/21/2022 02:15:05 PM (4 years ago)
Author:
amieiro
Message:

Translate: Introduce notification on rejection and opt-in/opt-out in a thread

See:

props akirk, spiraltee

File:
1 edited

Legend:

Unmodified
Added
Removed
  • sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/includes/class-gp-notifications.php

    r11837 r11921  
    1010class GP_Notifications {
    1111        /**
     12         * Stores the related comments to the first one when the validator makes a bulk rejection.
     13         *
     14         * @since 0.0.2
     15         * @var array
     16         */
     17        private static array $related_comments = array();
     18        /**
    1219         * Sends notifications when a new comment in the discussion is stored using the WP REST API.
    1320         *
     
    2734                                if ( ( '0' !== $comment->comment_parent ) ) { // Notify to the thread only if the comment is in a thread.
    2835                                        self::send_emails_to_thread_commenters( $comment, $comment_meta );
     36                                }
     37                                if ( ( '0' === $comment->comment_parent ) && array_key_exists( 'reject_reason', $comment_meta ) && ( ! empty( $comment_meta['reject_reason'] ) ) ) {  // Notify a rejection without parent comments.
     38                                        self::send_rejection_email_to_translator( $comment, $comment_meta );
    2939                                }
    3040                                $root_comment      = self::get_root_comment_in_a_thread( $comment );
     
    94104
    95105        /**
     106         * Sends the reject notification to the translator.
     107         *
     108         * @since 0.0.2
     109         *
     110         * @param WP_Comment $comment      The comment object.
     111         * @param array      $comment_meta The meta values for the comment.
     112         *
     113         * @return void
     114         */
     115        public static function send_rejection_email_to_translator( WP_Comment $comment, array $comment_meta ) {
     116                $translation_id = $comment_meta['translation_id'];
     117                $translation    = GP::$translation->get( $translation_id );
     118                $translator     = get_user_by( 'id', $translation->user_id_last_modified );
     119                if ( false === $translator ) {
     120                        $translator = get_user_by( 'id', $translation->user_id );
     121                }
     122                self::send_emails( $comment, $comment_meta, array( $translator->user_email ) );
     123        }
     124
     125        /**
    96126         * Sends an email to the GlotPress admins.
    97127         *
     
    119149         */
    120150        public static function send_emails_to_validators( WP_Comment $comment, array $comment_meta ) {
    121                 $project = self::get_project_to_translate( $comment );
     151                $post    = get_post( $comment->comment_post_ID );
     152                $project = self::get_project_from_post( $post );
    122153
    123154                $email_addresses = self::get_validators_email_addresses( $project->path );
     
    171202         * @since 0.0.2
    172203         *
    173          * @param array  $comments        Array with the parent comments to the posted comment.
    174          * @param string $email_address_to_ignore Email from the posted comment.
     204         * @param array       $comments                Array with the parent comments to the posted comment.
     205         * @param string|null $email_address_to_ignore Email from the posted comment.
    175206         *
    176207         * @return array The emails to be notified from the thread comments.
     
    252283                }
    253284
    254                 $admin_email_addresses = $wpdb->get_results(
    255                         "SELECT user_email FROM {$wpdb->users}
     285                try {
     286                        $admin_email_addresses = $wpdb->get_results(
     287                                "SELECT user_email FROM {$wpdb->users}
    256288                        INNER JOIN {$wpdb->gp_permissions}
    257289                        ON {$wpdb->users}.ID = {$wpdb->gp_permissions}.user_id
    258290                        WHERE action='admin'"
    259                 );
    260 
     291                        );
     292                } catch ( Exception $e ) {
     293                        $admin_email_addresses = array();
     294                }
    261295                foreach ( $admin_email_addresses as $admin ) {
    262296                        $email_addresses[] = $admin->user_email;
     
    295329                        return false;
    296330                }
     331                $original        = self::get_original( $comment );
    297332                $email_addresses = self::remove_commenter_email_address( $comment, $email_addresses );
     333                $email_addresses = self::remove_optout_discussion_email_addresses( $original->id, $email_addresses );
    298334
    299335                $headers = array(
     
    328364         */
    329365        public static function get_email_body( WP_Comment $comment, array $comment_meta ): string {
    330                 $project  = self::get_project_to_translate( $comment );
     366                $post     = get_post( $comment->comment_post_ID );
     367                $project  = self::get_project_from_post( $post );
    331368                $original = self::get_original( $comment );
    332369                $output   = '';
     
    351388                        )
    352389                ) . '<br/>';
     390                if ( ! empty( self::$related_comments ) ) {
     391                        $output .= wp_kses(
     392                        /* translators: The number of different translations related with the comment. */
     393                                sprintf( __( 'This comment affects to <strong>%1$d different translations</strong>.', 'glotpress' ), count( self::$related_comments ) + 1 ),
     394                                array(
     395                                        'a'      => array( 'href' => array() ),
     396                                        'strong' => array(),
     397                                )
     398                        ) . '<br/>';
     399                }
    353400                $output .= '<br>';
    354401                $output .= esc_html__( 'It would be nice if you have some time to review this comment and reply to it if needed.', 'glotpress' );
    355402                $output .= '<br><br>';
    356                 $output .= '- ' . wp_kses(
     403                if ( array_key_exists( 'locale', $comment_meta ) && ( ! empty( $comment_meta['locale'][0] ) ) ) {
     404                        /* translators: The translation locale for the comment. */
     405                        $output .= '- ' . wp_kses( sprintf( __( '<strong>Locale:</strong> %s', 'glotpress' ), $comment_meta['locale'][0] ), array( 'strong' => array() ) ) . '<br/>';
     406                }
     407                if ( empty( self::$related_comments ) ) { // Only show original and translation strings if we don't have related comments (bulk rejection).
     408                        /* translators: The original string to translate. */
     409                        $output .= '- ' . wp_kses( sprintf( __( '<strong>Original string:</strong> %s', 'glotpress' ), $original->singular ), array( 'strong' => array() ) ) . '<br/>';
     410                        if ( array_key_exists( 'translation_id', $comment_meta ) && $comment_meta['translation_id'][0] ) {
     411                                $translation_id = $comment_meta['translation_id'][0];
     412                                $translation    = GP::$translation->get( $translation_id );
     413                                // todo: add the plurals.
     414                                if ( ! is_null( $translation ) ) {
     415                                        /* translators: The translation string. */
     416                                        $output .= '- ' . wp_kses( sprintf( __( '<strong>Translation string:</strong> %s', 'glotpress' ), $translation->translation_0 ), array( 'strong' => array() ) ) . '<br/>';
     417                                }
     418                        }
     419                }
     420                /* translators: The comment made. */
     421                $output .= '- ' . wp_kses( sprintf( __( '<strong>Comment:</strong> %s', 'glotpress' ), $comment->comment_content ), array( 'strong' => array() ) ) . '<br/>';
     422                if ( empty( self::$related_comments ) ) {
     423                        $output .= '- ' . __( '<strong>Discussion URL:</strong>' ) . '<br/>';
     424                } else {
     425                        $output .= '- ' . __( '<strong>Discussion URLs:</strong>' ) . '<br/>';
     426                }
     427                $output .= '&nbsp;&nbsp;&nbsp; - ' . wp_kses(
    357428                        /* translators: The discussion URL where the user can find the comment. */
    358                         sprintf( __( '<strong>Discussion URL:</strong> <a href="%1$s">%1$s</a>', 'glotpress' ), $url ),
     429                        sprintf( __( '<a href="%1$s">%1$s</a>', 'glotpress' ), $url ),
    359430                        array(
    360431                                'strong' => array(),
     
    362433                        )
    363434                ) . '<br/>';
    364                 if ( array_key_exists( 'locale', $comment_meta ) && ( ! empty( $comment_meta['locale'][0] ) ) ) {
    365                         /* translators: The translation locale for the comment. */
    366                         $output .= '- ' . wp_kses( sprintf( __( '<strong>Locale:</strong> %s', 'glotpress' ), $comment_meta['locale'][0] ), array( 'strong' => array() ) ) . '<br/>';
    367                 }
    368                 /* translators: The original string to translate. */
    369                 $output .= '- ' . wp_kses( sprintf( __( '<strong>Original string:</strong> %s', 'glotpress' ), $original->singular ), array( 'strong' => array() ) ) . '<br/>';
    370                 if ( array_key_exists( 'translation_id', $comment_meta ) && $comment_meta['translation_id'][0] ) {
    371                         $translation_id = $comment_meta['translation_id'][0];
    372                         $translation    = GP::$translation->get( $translation_id );
    373                         // todo: add the plurals.
    374                         if ( ! is_null( $translation ) ) {
    375                                 /* translators: The translation string. */
    376                                 $output .= '- ' . wp_kses( sprintf( __( '<strong>Translation string:</strong> %s', 'glotpress' ), $translation->translation_0 ), array( 'strong' => array() ) ) . '<br/>';
    377                         }
    378                 }
    379                 /* translators: The comment made. */
    380                 $output .= '- ' . wp_kses( sprintf( __( '<strong>Comment:</strong> %s', 'glotpress' ), $comment->comment_content ), array( 'strong' => array() ) );
     435                foreach ( self::$related_comments as $related_comment ) {
     436                        $original = self::get_original( $related_comment );
     437                        $url      = GP_Route_Translation_Helpers::get_permalink( $project->path, $original->id );
     438                        $output  .= '&nbsp;&nbsp;&nbsp; - ' . wp_kses(
     439                                /* translators: The discussion URL where the user can find the comment. */
     440                                sprintf( __( '<a href="%1$s">%1$s</a>', 'glotpress' ), $url ),
     441                                array(
     442                                        'strong' => array(),
     443                                        'a'      => array( 'href' => array() ),
     444                                )
     445                        ) . '<br/>';
     446                }
    381447                $output .= '<br><br>';
    382448                $output .= esc_html__( 'Have a nice day!', 'glotpress' );
     
    434500
    435501        /**
     502         * Removes the opt-out emails in the current discussion.
     503         *
     504         * @since 0.0.2
     505         *
     506         * @param int   $original_id     The id of the original string used for the discussion.
     507         * @param array $email_addresses A list of emails.
     508         *
     509         * @return array
     510         */
     511        public static function remove_optout_discussion_email_addresses( int $original_id, array $email_addresses ): array {
     512                foreach ( $email_addresses as $email_address ) {
     513                        $user            = get_user_by( 'email', $email_address );
     514                        $is_user_opt_out = ! empty(
     515                                get_users(
     516                                        array(
     517                                                'meta_key'   => 'gp_opt_out',
     518                                                'meta_value' => $original_id,
     519                                                'include'    => array( $user->ID ),
     520                                        )
     521                                )
     522                        );
     523                        if ( $is_user_opt_out ) {
     524                                $index = array_search( $email_address, $email_addresses, true );
     525                                unset( $email_addresses[ $index ] );
     526                        }
     527                }
     528
     529                return array_values( $email_addresses );
     530        }
     531
     532        /**
    436533         * Gets the project that the translated string belongs to.
    437534         *
    438535         * @since 0.0.2
    439536         *
     537         * @param WP_Post $post The post object.
     538         *
     539         * @return GP_Project|bool The project that the translated string belongs to.
     540         */
     541        private static function get_project_from_post( WP_Post $post ) {
     542                $terms = wp_get_object_terms( $post->ID, Helper_Translation_Discussion::LINK_TAXONOMY, array( 'number' => 1 ) );
     543                if ( empty( $terms ) ) {
     544                        return false;
     545                }
     546
     547                $original   = GP::$original->get( $terms[0]->slug );
     548                $project_id = $original->project_id;
     549                $project    = GP::$project->get( $project_id );
     550
     551                return $project;
     552        }
     553
     554        /**
     555         * Adds a related comment (to the first one) when the validator makes a bulk rejection.
     556         *
     557         * @since 0.0.2
     558         *
     559         * @param WP_Comment $comment The related comment to add.
     560         *
     561         * @return void
     562         */
     563        public static function add_related_comment( WP_Comment $comment ) {
     564                self::$related_comments[] = $comment;
     565        }
     566
     567        /**
     568         * Gets the project the original_id belongs to.
     569         *
     570         * @since 0.0.2
     571         *
     572         * @param int $original_id The id of the original string used for the discussion.
     573         *
     574         * @return GP_Project The project the original_id belongs to.
     575         */
     576        public static function get_project_from_original_id( int $original_id ): GP_Project {
     577                $original = GP::$original->get( $original_id );
     578                return GP::$project->get( $original->project_id );
     579        }
     580
     581        /**
     582         * Gets the original string that the translated string belongs to.
     583         *
     584         * @since 0.0.2
     585         *
    440586         * @param WP_Comment $comment The comment object.
    441587         *
    442          * @return GP_Project|bool The project that the translated string belongs to.
    443          */
    444         private static function get_project_to_translate( WP_Comment $comment ) {
     588         * @return GP_Thing|false The original string that the translated string belongs to.
     589         */
     590        public static function get_original( WP_Comment $comment ) {
    445591                $post_id = $comment->comment_post_ID;
    446592                $terms   = wp_get_object_terms( $post_id, Helper_Translation_Discussion::LINK_TAXONOMY, array( 'number' => 1 ) );
     
    449595                }
    450596
    451                 $original   = GP::$original->get( $terms[0]->slug );
    452                 $project_id = $original->project_id;
    453                 $project    = GP::$project->get( $project_id );
    454 
    455                 return $project;
    456         }
    457 
    458         /**
    459          * Gets the original string that the translated string belongs to.
    460          *
    461          * @since 0.0.2
    462          *
    463          * @param WP_Comment $comment The comment object.
    464          *
    465          * @return GP_Thing|false The original string that the translated string belongs to.
    466          */
    467         private static function get_original( WP_Comment $comment ) {
    468                 $post_id = $comment->comment_post_ID;
    469                 $terms   = wp_get_object_terms( $post_id, Helper_Translation_Discussion::LINK_TAXONOMY, array( 'number' => 1 ) );
    470                 if ( empty( $terms ) ) {
     597                return GP::$original->get( $terms[0]->slug );
     598        }
     599
     600        /**
     601         * Gets the post_id for the discussion of an original_id.
     602         *
     603         * If the post doesn't exist, the result is 0.
     604         *
     605         * @param int $original_id The id of the original string used for the discussion.
     606         *
     607         * @return int The post_id for the discussion of an original_id.
     608         */
     609        public static function get_post_id( int $original_id ): int {
     610                        $gp_posts = get_posts(
     611                                array(
     612                                        'tax_query'        => array(
     613                                                array(
     614                                                        'taxonomy' => Helper_Translation_Discussion::LINK_TAXONOMY,
     615                                                        'terms'    => $original_id,
     616                                                        'field'    => 'slug',
     617                                                ),
     618                                        ),
     619                                        'post_type'        => Helper_Translation_Discussion::POST_TYPE,
     620                                        'posts_per_page'   => 1,
     621                                        'post_status'      => Helper_Translation_Discussion::POST_STATUS,
     622                                        'suppress_filters' => false,
     623                                )
     624                        );
     625
     626                return ! empty( $gp_posts ) ? $gp_posts[0]->ID : 0;
     627        }
     628
     629        /**
     630         * Returns if the given user is an GlotPress admin or not.
     631         *
     632         * @since 0.0.2
     633         *
     634         * @param WP_User $user A user object.
     635         *
     636         * @return bool
     637         */
     638        public static function is_user_an_gp_admin( WP_User $user ): bool {
     639                global $wpdb;
     640                try {
     641                        $db_email_addresses = $wpdb->get_results(
     642                                "
     643                        SELECT user_email FROM {$wpdb->users}
     644                        INNER JOIN {$wpdb->gp_permissions}
     645                        ON {$wpdb->users}.ID = {$wpdb->gp_permissions}.user_id
     646                        WHERE action='admin'",
     647                                ARRAY_N
     648                        );
     649                        foreach ( $db_email_addresses as $email_address ) {
     650                                $email_addresses[] = $email_address[0];
     651                        }
     652                } catch ( Exception $e ) {
     653                        $email_addresses = array();
     654                }
     655                if ( empty( $email_addresses ) || empty( array_intersect( array( $user->user_email ), $email_addresses ) ) ) {
    471656                        return false;
    472657                }
    473 
    474                 return GP::$original->get( $terms[0]->slug );
     658                return true;
     659        }
     660
     661        /**
     662         * Returns if the given user is an GlotPress validator for the post or not.
     663         *
     664         * @since 0.0.2
     665         *
     666         * @param WP_User $user        A user object.
     667         * @param int     $original_id The id of the original string used for the discussion.
     668         *
     669         * @return bool
     670         */
     671        public static function is_user_an_gp_validator( WP_User $user, int $original_id ): bool {
     672                $project         = self::get_project_from_original_id( $original_id );
     673                $email_addresses = self::get_validators_email_addresses( $project->path );
     674                if ( empty( $email_addresses ) || empty( array_intersect( array( $user->user_email ), $email_addresses ) ) ) {
     675                        return false;
     676                }
     677                return true;
     678        }
     679
     680        /**
     681         * Indicates whether an e-mail address is opt-out in a discussion.
     682         *
     683         * @since 0.0.2
     684         *
     685         * @param int     $original_id The id of the original string used for the discussion.
     686         * @param WP_User $user        A user object.
     687         *
     688         * @return bool True if the user has opt-out, otherwise false.
     689         */
     690        public static function is_user_opt_out_in_discussion( int $original_id, WP_User $user ): bool {
     691                return ! empty(
     692                        get_users(
     693                                array(
     694                                        'meta_key'   => 'gp_opt_out',
     695                                        'meta_value' => $original_id,
     696                                        'include'    => array( $user->ID ),
     697                                )
     698                        )
     699                );
     700        }
     701
     702        /**
     703         * Gets the opt-in/oup-out message to show at the bottom of the discussions.
     704         *
     705         * @since 0.0.2
     706         *
     707         * @param int $original_id The id of the original string used for the discussion.
     708         *
     709         * @return string The opt-in/oup-out message to show at the bottom of the discussions.
     710         */
     711        public static function optin_message_for_each_discussion( int $original_id ): string {
     712                $post_id = self::get_post_id( $original_id );
     713                /**
     714                 * Filters the optin message that will be showed in each discussion.
     715                 *
     716                 * @since 0.0.2
     717                 *
     718                 * @param string $message     The opt-in/oup-out message to show at the bottom of the discussions.
     719                 * @param int    $original_id The id of the original string used for the discussion.
     720                 */
     721                $message = apply_filters( 'gp_get_optin_message_for_each_discussion', '', $original_id );
     722                if ( $message ) {
     723                        return $message;
     724                }
     725                $user            = wp_get_current_user();
     726                $is_user_opt_out = self::is_user_opt_out_in_discussion( $original_id, $user );
     727                if ( ! $is_user_opt_out ) {
     728                        $comments = get_comments(
     729                                array(
     730                                        'user_id'            => $user->ID,
     731                                        'post_id'            => $post_id,
     732                                        'status'             => 'approve',
     733                                        'type'               => 'comment',
     734                                        'include_unapproved' => array( $user->ID ),
     735                                )
     736                        );
     737                }
     738
     739                if ( $is_user_opt_out ) {  // Opt-out user.
     740                        $output  = __( 'You will not receive notifications for this discussion because you have opt-out to get notifications for it. ' );
     741                        $output .= ' <a href="#" class="opt-in-discussion" data-original-id="' . $original_id . '" data-opt-type="optin">' . __( 'Start receiving notifications for this discussion.' ) . '</a>';
     742                        return $output;
     743                }
     744                if ( $comments && ( ! self::is_user_an_gp_admin( $user ) ) && ( ! self::is_user_an_gp_validator( $user, $original_id ) ) ) { // Regular user with comments.
     745                        $output  = __( 'You are going to receive notifications for the threads where you have participated. ' );
     746                        $output .= ' <a href="#" class="opt-out-discussion" data-original-id="' . $original_id . '" data-opt-type="optout">' . __( 'Stop receiving notifications for this discussion.' ) . '</a>';
     747                        return $output;
     748                }
     749                if ( self::is_user_an_gp_admin( $user ) && self::is_user_an_gp_validator( $user, $original_id ) ) {  // Admin and validator user.
     750                        $output  = __( 'You are going to receive notifications because you are a GlotPress administrator and a validator for this project and language. ' );
     751                        $output .= __( 'You will not receive notifications if another administrator or another validator participate in a thread where you do not take part. ' );
     752                        $output .= ' <a href="#" class="opt-out-discussion" data-original-id="' . $original_id . '" data-opt-type="optout">' . __( 'Stop receiving notifications for this discussion.' ) . '</a>';
     753                        return $output;
     754                }
     755                if ( self::is_user_an_gp_admin( $user ) ) {   // Admin user.
     756                        $output  = __( 'You are going to receive notifications because you are a GlotPress administrator. ' );
     757                        $output .= __( 'You will not receive notifications if another administrator participate in a thread where you do not take part. ' );
     758                        $output .= ' <a href="#" class="opt-out-discussion" data-original-id="' . $original_id . '" data-opt-type="optout">' . __( 'Stop receiving notifications for this discussion.' ) . '</a>';
     759                        return $output;
     760                }
     761                if ( self::is_user_an_gp_validator( $user, $original_id ) ) { // Validator user.
     762                        $output  = __( 'You are going to receive notifications because you are a GlotPress validator for this project and language. ' );
     763                        $output .= __( 'You will not receive notifications if another validator participate in a thread where you do not take part. ' );
     764                        $output .= ' <a href="#" class="opt-out-discussion" data-original-id="' . $original_id . '" data-opt-type="optout">' . __( 'Stop receiving notifications for this discussion.' ) . '</a>';
     765                        return $output;
     766                }
     767                return __( 'You will not receive notifications for this discussion. We will send you notifications as soon as you get involved.' ); // Regular user without comments.
     768
    475769        }
    476770}
Note: See TracChangeset for help on using the changeset viewer.