roles ); return array_values( array_diff( $all, array( 'administrator' ) ) ); } /** * The roles we really include: the ones ticked, PLUS any role created after the owner last saved * (other plugins add their own roles, and those users must not silently vanish). Never administrators. */ public static function effective_roles() { $saved = get_option( 'postaramail_exist_roles', null ); if ( ! is_array( $saved ) ) { return self::roles(); } if ( ! $saved ) { return array(); // the owner ticked nothing on purpose: nobody, whatever roles exist } $all = array_keys( wp_roles()->roles ); $known = get_option( 'postaramail_roles_known', null ); if ( ! is_array( $known ) ) { // Saved before this was tracked: the screen then listed only WordPress's own roles (and WooCommerce's). $known = array_values( array_intersect( $all, array( 'administrator', 'editor', 'author', 'contributor', 'subscriber', 'customer', 'shop_manager' ) ) ); } $new = array_diff( array_diff( $all, $known ), array( 'administrator' ) ); return array_values( array_unique( array_merge( $saved, $new ) ) ); } /** Remember which roles existed when the owner last chose, so later roles can be told apart. */ public static function remember_roles() { update_option( 'postaramail_roles_known', array_keys( wp_roles()->roles ), false ); delete_transient( 'postaramail_breakdown' ); } /** May we list / ask this user? Users with NO role count too (some plugins create them). Administrators only if ticked. */ public static function role_ok( $user, $roles = null ) { $roles = null === $roles ? self::effective_roles() : $roles; if ( ! $roles ) { return false; // nothing ticked = nobody } $r = (array) $user->roles; if ( in_array( 'administrator', $r, true ) && ! in_array( 'administrator', $roles, true ) ) { return false; } return ! $r || (bool) array_intersect( $r, $roles ); } private static function in_roles( $user ) { return self::role_ok( $user ); } /** * Users that have none of the given meta keys and pass the role rule. The role check is done here (not in the * query) so users without a role are not lost; it scans in pages so excluded users never block the rest. */ public static function scan( $skip_meta, $limit ) { $roles = self::effective_roles(); if ( ! $roles ) { return array(); } $out = array(); $found = 0; $paged = 1; while ( $found < $limit && $paged <= 25 ) { $mq = array( 'relation' => 'AND' ); foreach ( $skip_meta as $key ) { $mq[] = array( 'key' => $key, 'compare' => 'NOT EXISTS', ); } $ids = get_users( array( 'fields' => 'ID', 'number' => 200, 'paged' => $paged, 'orderby' => 'ID', 'order' => 'ASC', 'meta_query' => $mq, ) ); if ( ! $ids ) { break; } foreach ( $ids as $id ) { $u = get_userdata( $id ); if ( $u && is_email( $u->user_email ) && self::role_ok( $u, $roles ) ) { $out[] = (int) $id; ++$found; if ( count( $out ) >= $limit ) { break; } } } ++$paged; } return $out; } /** User ids that have not answered and were not already asked by email (the database does the filtering). */ public static function pending_ids( $limit = 5000 ) { return self::scan( array( self::META, self::ASKED ), $limit ); } public static function counts( $with_pending = true ) { global $wpdb; $yes = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->usermeta} WHERE meta_key = %s AND meta_value = 'yes'", self::META ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery $no = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->usermeta} WHERE meta_key = %s AND meta_value = 'no'", self::META ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery return array( 'yes' => $yes, 'no' => $no, 'pending' => $with_pending ? count( self::pending_ids() ) : 0, 'queued' => count( (array) get_option( self::QUEUE, array() ) ), ); } // ---------- Recording an answer ---------- public static function record( $user_id, $yes, $how ) { update_user_meta( $user_id, self::META, $yes ? 'yes' : 'no' ); $u = get_userdata( $user_id ); if ( ! $u || ! is_email( $u->user_email ) ) { return; } if ( ! $yes ) { PostaraMail_Subscriber::decline( $u->user_email ); // shows as "Declined" in PostaraMail: never emailed return; } // Their saved country if we have one, otherwise where they are right now (they just clicked). $country = PostaraMail_Geo::for_user( $user_id ); $geo = PostaraMail_Geo::for_request(); PostaraMail_Subscriber::subscribe( array( 'email' => $u->user_email, 'first' => $u->first_name ? $u->first_name : '', 'last' => $u->last_name ? $u->last_name : '', 'automation' => (int) get_option( 'postaramail_default_automation', 0 ), 'source' => 'existing', 'consent' => 'Existing member chose "Yes" to news by email (' . $how . ') on ' . gmdate( 'Y-m-d' ), 'method' => 'optin', 'confirmed' => true, // they chose yes themselves while logged in or via their own signed link 'country' => $country ? $country : $geo['country'], 'ip' => $country ? '' : $geo['ip'], ) ); } // ---------- 1. The prompt after login ---------- /** Whether the logged-in visitor should see the "get news by email?" prompt. */ private static function should_ask() { if ( ! is_user_logged_in() ) { return false; } $user = wp_get_current_user(); return ! ( get_user_meta( $user->ID, self::META, true ) || ! self::in_roles( $user ) || (int) get_user_meta( $user->ID, self::SNOOZE, true ) > time() ); } /** Loads the prompt's style and script, only for people who will actually see it. */ public static function banner_assets() { if ( ! self::should_ask() ) { return; } wp_enqueue_style( 'postaramail-ask', POSTARAMAIL_WP_URL . 'assets/ask.css', array(), POSTARAMAIL_WP_VERSION ); wp_enqueue_script( 'postaramail-ask', POSTARAMAIL_WP_URL . 'assets/ask.js', array(), POSTARAMAIL_WP_VERSION, true ); wp_localize_script( 'postaramail-ask', 'PostaraMailAsk', array( 'url' => esc_url_raw( rest_url( 'postaramail/v1/optin' ) ), 'nonce' => wp_create_nonce( 'wp_rest' ), ) ); } public static function banner() { if ( ! self::should_ask() ) { return; } $site = wp_strip_all_tags( get_bloginfo( 'name' ) ); ?>
—
' . sprintf( /* translators: %s: first name */ esc_html__( 'Hi %s,', 'postaramail' ), esc_html( $first ) ) . '
' . '' . sprintf( /* translators: %s: site name */ esc_html__( 'You have an account on %s. We would like to send you occasional news and offers by email. Would you like that?', 'postaramail' ), '' . esc_html( $site ) . '' ) . '
' . '' . esc_html__( 'Yes, keep me posted', 'postaramail' ) . '' . ' ' . esc_html__( 'No thanks', 'postaramail' ) . '
' . '' . esc_html__( 'If you do nothing, we will not add you to our email list and we will not ask again. You can unsubscribe at any time.', 'postaramail' ) . '
' . '' . esc_html( $site ) . ' · ' . esc_url( home_url( '/' ) ) . ( $privacy ? ' · ' . esc_html__( 'Privacy policy', 'postaramail' ) . '' : '' ) . '
'; // One question per person, ever: mark it first so a retry can never send it twice. update_user_meta( $id, self::ASKED, time() ); wp_mail( $u->user_email, sprintf( /* translators: %s: site name */ __( 'Do you want news from %s?', 'postaramail' ), $site ), $body, array( 'Content-Type: text/html; charset=UTF-8' ) ); } update_option( self::QUEUE, $queue, false ); if ( ! $queue ) { wp_clear_scheduled_hook( self::HOOK ); } } /** The Yes / No links in the email. */ public static function handle_link() { if ( empty( $_GET['pm_optin'] ) || empty( $_GET['u'] ) || empty( $_GET['t'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- signed link instead of a nonce. return; } $answer = 'yes' === $_GET['pm_optin'] ? 'yes' : 'no'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended $id = (int) $_GET['u']; // phpcs:ignore WordPress.Security.NonceVerification.Recommended $token = sanitize_text_field( wp_unslash( $_GET['t'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( ! hash_equals( self::sign( $id, $answer ), $token ) || ! get_userdata( $id ) ) { wp_die( esc_html__( 'This link is not valid.', 'postaramail' ), esc_html__( 'Link not valid', 'postaramail' ), array( 'response' => 400 ) ); } self::record( $id, 'yes' === $answer, 'email link' ); $msg = 'yes' === $answer ? __( 'Thank you! You are subscribed. You can unsubscribe at any time from any email we send.', 'postaramail' ) : __( 'No problem. We will not add you to our email list.', 'postaramail' ); wp_die( esc_html( $msg ) . '', esc_html( get_bloginfo( 'name' ) ), array( 'response' => 200 ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } }