ivated = get_option( 'wpforms_activated', [] ); $pro_activated_at = isset( $activated['pro'] ) ? (int) $activated['pro'] : 0; if ( $pro_activated_at <= 0 ) { return -1; } return (int) floor( ( time() - $pro_activated_at ) / DAY_IN_SECONDS ); } /** * Whether the site has at least one form. * * @since 1.10.1.1 * * @return bool */ private function has_forms(): bool { $form_obj = wpforms()->obj( 'form' ); if ( ! $form_obj ) { return false; } $forms = $form_obj->get( '', [ 'fields' => 'ids', 'numberposts' => 1, ] ); return ! empty( $forms ); } /** * Whether the site has zero entries across all forms. * * @since 1.10.1.1 * * @return bool */ private function has_zero_entries(): bool { $entry_obj = wpforms()->obj( 'entry' ); if ( ! $entry_obj ) { return false; } return (int) $entry_obj->get_entries( [], true ) === 0; } /** * Whether any form suddenly went quiet: active for several consecutive weeks * and then zero entries in the week being reported. * * @since 2.0.0 * * @return bool */ private function has_engagement_drop(): bool { return $this->detect_drop( $this->get_weekly_counts_by_form() ); } /** * Per-form entry counts for the reported week and the preceding streak weeks. * * @since 2.0.0 * * @return array */ private function get_weekly_counts_by_form(): array { // The reported week ends on the previous Sunday, matching Summaries::get_entries(). $last_sunday = date_create( 'previous sunday' ); $reports = new EntriesCount(); $weekly = []; for ( $week = 0; $week <= self::DROP_STREAK_WEEKS; $week++ ) { $week_end = ( clone $last_sunday )->modify( '-' . ( $week * self::WINDOW_DAYS ) . ' days' ); $counts = $reports->get_by( 'form', 0, self::WINDOW_DAYS, $week_end->format( 'Y-m-d' ) ); // get_by( 'form' ) is keyed by form ID; wp_list_pluck keeps those keys (no re-index). $weekly[ $week ] = wp_list_pluck( $counts, 'count' ); } return $weekly; } /** * Detect an engagement drop in the given per-week counts. * * @since 2.0.0 * * @param array $weekly Per-form counts keyed by week index (0 = reported week). * * @return bool */ private function detect_drop( array $weekly ): bool { // Need the reported week plus a full streak of preceding weeks to decide. if ( count( $weekly ) <= self::DROP_STREAK_WEEKS ) { return false; } $reported_week = $weekly[0] ?? []; // A dropped form must have been active in the most recent streak week, // so candidates come from that week alone. foreach ( $weekly[1] ?? [] as $form_id => $count ) { // Skip forms below the threshold last week, or still active this week. if ( $count < self::DROP_MIN_ENTRIES || ! empty( $reported_week[ $form_id ] ) ) { continue; } // Confirm the threshold held across the remaining streak weeks. $has_active_streak = true; for ( $week = 2; $week <= self::DROP_STREAK_WEEKS; $week++ ) { if ( ( $weekly[ $week ][ $form_id ] ?? 0 ) < self::DROP_MIN_ENTRIES ) { $has_active_streak = false; break; } } if ( $has_active_streak ) { return true; } } return false; } /** * Build the alert template args for the resolved variant. * * @since 1.10.1.1 * * @param string $variant Variant slug. * * @return array Alert args (slug, title, content, button_text, button_url). */ private function build_args( string $variant ): array { $utm_content = self::VARIANTS[ $variant ]['utm_content']; $utm_medium = 'email'; $utm_campaign = 'weekly-summary-reengagement'; // Copy table per issue #17326. $copy = [ 'phase-a-early' => [ 'title' => __( 'Your forms aren\'t collecting entries yet', 'wpforms' ), 'content' => __( 'Forms only collect entries when they\'re embedded on a page visitors can see. Check that each form is added to a page (via shortcode or block) and that the page actually gets traffic.', 'wpforms' ), 'button_text' => __( 'Check Your Forms', 'wpforms' ), 'admin_path' => 'admin.php?page=wpforms-overview', ], 'phase-a-late' => [ 'title' => __( 'Your forms have been quiet for a while', 'wpforms' ), 'content' => __( 'After a few months at zero, the most common cause is page placement. Try moving your form to a higher-traffic page: homepage, pricing, or a top blog post.', 'wpforms' ), 'button_text' => __( 'Check Your Forms', 'wpforms' ), 'admin_path' => 'admin.php?page=wpforms-overview', ], 'phase-b' => [ 'title' => __( 'Still no entries? Try a different form type.', 'wpforms' ), 'content' => __( 'When placement isn\'t the issue, matching the form to visitor intent usually unsticks things: quote request for services, newsletter signup for content, or demo booking for software.', 'wpforms' ), 'button_text' => __( 'Browse Templates', 'wpforms' ), 'admin_path' => 'admin.php?page=wpforms-builder', ], ][ $variant ]; $button_url = add_query_arg( [ 'utm_source' => 'wpforms-plugin', 'utm_medium' => $utm_medium, 'utm_campaign' => $utm_campaign, 'utm_content' => $utm_content, ], admin_url( $copy['admin_path'] ) ); return [ 'slug' => $variant, 'title' => $copy['title'], 'content' => $copy['content'], 'button_text' => $copy['button_text'], 'button_url' => $button_url, ]; } /** * Build the alert template args for the engagement-drop state. * * @since 2.0.0 * * @return array Alert args (slug, title, content, content_plain, button_text, button_url). */ private function build_drop_args(): array { $button_url = add_query_arg( [ 'utm_source' => 'wpforms-plugin', 'utm_medium' => 'email', 'utm_campaign' => 'weekly-summary-reengagement', 'utm_content' => self::DROP_VARIANT_SLUG, ], admin_url( 'admin.php?page=wpforms-overview' ) ); return [ 'slug' => self::DROP_VARIANT_SLUG, 'title' => __( 'Your forms have gone quiet this week.', 'wpforms' ), 'content' => __( 'Your submissions dropped compared to recent activity. If this is unexpected, take a look at your form analytics or reach out to our support team.', 'wpforms' ), 'content_plain' => __( 'Your submissions dropped compared to recent activity. If this is unexpected, take a look at your form analytics or reach out to our support team at support@wpforms.com.', 'wpforms' ), 'button_text' => __( 'Check Your Forms', 'wpforms' ), 'button_url' => $button_url, ]; } /** * Inject the re-engagement alert args into the summary template when conditions are met. * * @since 1.10.1.1 * * @param Summary $template Summary email template. * * @return Summary */ public function maybe_add_alert_args( $template ) { if ( ! $template instanceof Summary ) { return $template; } $alert_args = $this->get_alert_args(); if ( $alert_args === null ) { return $template; } $template->set_args( [ 'body' => [ 'reengagement_alert' => $alert_args, ], ] ); return $template; } /** * Resolve the alert args for whichever conditional state applies, or null when none does. * * @since 2.0.0 * * @return array|null Alert args or null. */ private function get_alert_args(): ?array { if ( $this->has_engagement_drop() ) { return $this->build_drop_args(); } $variant = $this->get_active_variant(); return $variant === null ? null : $this->build_args( $variant ); } }