0, 'shown' => 0, 'waiting' => 0, 'yes' => 0, 'no' => 0, 'admins' => 0, 'role' => 0, 'email' => 0, 'list' => array(), 'roles' => array(), ); $add = function ( $user, $why ) use ( &$b, $list_max ) { if ( count( $b['list'] ) < $list_max ) { $b['list'][] = array( $user ? (string) $user->user_email : '', $why ); } }; for ( $paged = 1; $paged <= 10; $paged++ ) { $ids = get_users( array( 'fields' => 'ID', 'number' => 200, 'paged' => $paged, 'orderby' => 'ID', 'order' => 'ASC', ) ); if ( ! $ids ) { break; } foreach ( $ids as $id ) { $u = get_userdata( $id ); if ( ! $u ) { continue; } ++$b['checked']; $ans = get_user_meta( $id, PostaraMail_Existing::META, true ); if ( 'yes' === $ans ) { ++$b['yes']; continue; } if ( 'no' === $ans ) { ++$b['no']; $add( $u, __( 'said no (shown as Declined)', 'postaramail' ) ); continue; } if ( ! is_email( $u->user_email ) ) { ++$b['email']; $add( $u, __( 'no valid email address', 'postaramail' ) ); continue; } if ( get_user_meta( $id, self::PUSHED, true ) ) { ++$b['shown']; continue; } if ( PostaraMail_Existing::role_ok( $u, $roles ) ) { ++$b['waiting']; continue; } if ( in_array( 'administrator', (array) $u->roles, true ) ) { ++$b['admins']; $add( $u, __( 'administrator (never listed unless you tick Administrator)', 'postaramail' ) ); } else { ++$b['role']; $r = $u->roles ? reset( $u->roles ) : '?'; $b['roles'][ $r ] = isset( $b['roles'][ $r ] ) ? $b['roles'][ $r ] + 1 : 1; $add( $u, sprintf( /* translators: %s: role */ __( 'role ā€œ%sā€ is not ticked', 'postaramail' ), $r ) ); } } if ( count( $ids ) < 200 ) { break; } } set_transient( 'postaramail_breakdown', $b, 5 * MINUTE_IN_SECONDS ); return $b; } /** Roles that have users but are NOT included (so the owner can see why someone is missing). */ public static function roles_not_included() { $b = self::breakdown(); return $b['roles']; } public static function pushed_count() { global $wpdb; return (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->usermeta} WHERE meta_key = %s", self::PUSHED ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery } /** Sent to PostaraMail and still unanswered: these are the "not asked yet" contacts there. */ public static function pending_count() { global $wpdb; return (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->usermeta} p LEFT JOIN {$wpdb->usermeta} o ON o.user_id = p.user_id AND o.meta_key = %s WHERE p.meta_key = %s AND o.user_id IS NULL", PostaraMail_Existing::META, self::PUSHED ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery,WordPress.DB.PreparedSQL } // ---------- Sending ---------- /** * Sends these users to PostaraMail as "not asked yet". Returns true when PostaraMail accepted them * (or there was nothing to send), false when it should be retried. */ public static function push_ids( $ids, $geo = null, $timeout = 25 ) { $rows = array(); foreach ( $ids as $id ) { if ( get_user_meta( $id, PostaraMail_Existing::META, true ) || get_user_meta( $id, self::PUSHED, true ) ) { continue; // already answered (for example subscribed by the registration notice) or already sent } $u = get_userdata( $id ); if ( ! $u || ! is_email( $u->user_email ) ) { update_user_meta( $id, self::PUSHED, 1 ); // nothing to send; don't look at it again continue; } if ( ! PostaraMail_Existing::role_ok( $u ) ) { continue; } $country = PostaraMail_Geo::for_user( $id ); $row = array( 'email' => $u->user_email, 'first_name' => $u->first_name ? $u->first_name : '', 'last_name' => $u->last_name ? $u->last_name : '', 'country' => $country, 'source' => ( class_exists( 'WooCommerce' ) && get_user_meta( $id, 'billing_country', true ) ) ? 'woocommerce' : 'wordpress', // a shop customer ); if ( ! $country && $geo ) { $row['country'] = $geo['country']; if ( ! $geo['country'] && $geo['ip'] ) { $row['ip'] = $geo['ip']; // PostaraMail turns it into a country and discards it } } if ( empty( $row['country'] ) && empty( $row['ip'] ) ) { $known_ip = PostaraMail_Geo::ip_for_user( $id ); // last login address, when the site kept one if ( $known_ip ) { $row['ip'] = $known_ip; } } $rows[ $id ] = $row; } if ( ! $rows ) { return true; } $r = PostaraMail_API::contacts_sync( $rows, array(), $timeout ); if ( ! $r['ok'] ) { if ( ! $r['retry'] ) { PostaraMail_Subscriber::remember_error( $r['message'] ? $r['message'] : $r['code'] ); } return false; } foreach ( array_keys( $rows ) as $id ) { update_user_meta( $id, self::PUSHED, time() ); } delete_transient( 'postaramail_me' ); delete_transient( 'postaramail_site_users' ); delete_transient( 'postaramail_breakdown' ); return true; } // ---------- Countries ---------- /** * Sends users who are shown in PostaraMail without a country once more, now with every country clue the site has. * Only people still "not asked yet" are updated there; anyone who already said yes or no is left alone. * Returns how many users will be sent again. */ public static function rescan_countries() { global $wpdb; $have = $wpdb->get_col( "SELECT DISTINCT user_id FROM {$wpdb->usermeta} WHERE meta_key IN ('billing_country','shipping_country') AND CHAR_LENGTH(meta_value) = 2" ); // phpcs:ignore WordPress.DB $sent = $wpdb->get_col( $wpdb->prepare( "SELECT user_id FROM {$wpdb->usermeta} WHERE meta_key = %s", self::PUSHED ) ); // phpcs:ignore WordPress.DB $n = 0; foreach ( array_diff( array_map( 'intval', $sent ), array_map( 'intval', $have ) ) as $uid ) { delete_user_meta( $uid, self::PUSHED ); ++$n; } delete_transient( 'postaramail_site_users' ); delete_transient( 'postaramail_breakdown' ); if ( $n ) { self::schedule_soon(); } return $n; } /** Once after updating to 2.2: look up countries again with the new clues. */ public static function maybe_rescan() { if ( get_option( 'postaramail_geo_scan' ) !== '2' && self::enabled() ) { update_option( 'postaramail_geo_scan', '2', false ); self::rescan_countries(); } } /** Sends up to $limit waiting users. Returns how many were sent (0 if none or failed). */ public static function push_waiting( $limit = 100 ) { $ids = self::waiting_ids( $limit ); if ( ! $ids ) { return 0; } return self::push_ids( $ids ) ? count( $ids ) : 0; } /** The background task (every 15 minutes, or soon after a failed send). */ public static function push_batch( $unused = null ) { update_option( 'postaramail_cron_last', time(), false ); // lets the screen tell whether background tasks run at all if ( ! self::enabled() || get_transient( 'postaramail_push_lock' ) ) { return; } set_transient( 'postaramail_push_lock', 1, 120 ); $n = self::push_waiting( 100 ); delete_transient( 'postaramail_push_lock' ); if ( 100 === $n && self::waiting_count( 1 ) ) { wp_schedule_single_event( time() + 20, self::HOOK, array( time() ) ); // more waiting: go again } } /** "Send now": everyone waiting, up to 500, within about 20 seconds. Returns [ sent, error ]. */ public static function push_now() { if ( ! self::enabled() ) { return array( 0, __( 'Showing existing users in PostaraMail is switched off, or the site is not connected.', 'postaramail' ) ); } $total = 0; $stop = time() + 20; for ( $i = 0; $i < 5 && time() < $stop; $i++ ) { $ids = self::waiting_ids( 100 ); if ( ! $ids ) { break; } if ( ! self::push_ids( $ids ) ) { $e = get_option( 'postaramail_last_error' ); return array( $total, is_array( $e ) && ! empty( $e['message'] ) ? $e['message'] : __( 'PostaraMail could not be reached. Try again in a moment.', 'postaramail' ) ); } $total += count( $ids ); } return array( $total, '' ); } }