JFIF    $         d d  7    * Z qn )ZP 0   !1 A "#a$2Qr   D8 a Ri [f  \m I ykI w0 c u F c Rı?lO7 к _f˓ [C $殷WF< _W ԣ s Kc ëI zyQy /_LK ℂ ;C",p F A : /] =H  ~ , l s / 9  ć: [=/#f ; ) x { ٛE Q )~     = 𘙲r*2~  a _V = '  ku m F  D } KYYC )( { * g& f `툪 r y ` = ^ c J. I ]( * `wq  1dđ #̩ ͑ 0  ; H ] u 搂 @:~ ו K L  N s h } O I R* 8 :2 ! lD JV o (  3 = M ( z Ȱ+i *N A  r6K n S l ) ! J J Ӂ* %  ݉?|D } d5: eP0 R; {$X' x F @ .ÊB {,WJ uQ ɲRI ;9 Q E琯 6 2fT .DU J ;*c  P A\IL Nj! J ۱+ O\ ͔ ] ޒ S߼ J ȧ c%A N ol  ՎprU L Zԛe r E2= X D Xg VQeӓ k y P7 U* o m Q I s ,K `) 6\G3t?pgjr m ۛجw luGt f  h 9uyP 0D;Uڽ"OXl i f $ ) &|ML 0 Z rm1 [ H XP l P R0  'G=i 2N +0 e 2 ]] 9 VT P  O׮ 7 h(F * 癈 ' = Q VZD F,d ߬ ~ T X G[ `le 69C R(!S2! P  < 0 x <     !1AQ "Raq 02Br #S CTb  ? Ζ "]mH5WR 7 k.ۛ ! } Q~ + yԏ z |@ T20 S~ K e k *zF f^2X *( @ 8 r ? C I uI|֓>^Ex LgNU Y+{.R Ѫ τV׸ YT D I 6 2 '8Y2  7'\T P.6 d &˦ @V q i  |8 -O Ε  ] ʔ U=   T L 8= ;6c |  !qfF3 a ů& ~$ l} 'NW U s$U k^S V : U# 6w +  +s &r+n ڐ   { @ 29 gL u " T Ù  M=6  (^ "7 r}=6Yݾ lC u hquymp Ǧ G jhs ǜNl ɻ} o 7 #S6 a w 4 !  OS  r D  5 7 % | ?x> L |  / nD 6?/8 w # [ ) L 7 +6〼T A T g   !  %5  Mm Z /c  -{1_Je "| ^$ 'O  & ޱմ < H vNqϡQ$ < 6 &Nݛ ֨tǻN (9 n ru~'όN sMG& ,uH Kw *Ī 5@  u6  BԒ  i YN * f 0 _ GhKW u  lT <nj- 0{  M L~ | /a v\ E = ?1 \ɟuq Ђ Y O a K ޡP q  $ H8o Q˼EV ~ k 0 h' L  Pp M -M"  m^ !6l nմ 쮝C U T . ! :vMǨb h ډ Vc r @񄞅~p   #   !1QAa q  ? AL0L  x LU 4 d ; x   " 16ad0 7.\ߋ 0  = P l v/ " c A Ay A|2pO8"d MV #1] = \ " ˡ ] k v I9 d ۓ & q ) ǐq E ꞅ v "P )q X  &Q  З non]я u ג./ ; gZ$ M QР 2M ]h ni 0  P $ێ 3CɏGjώy : m c # S_ l@ '_ \m|C 2`K _  1 cIvGc $? PCQ S+D ? K aײ k Fr b 8 ύ NEدg ĝ U 3  6 #Ik+ v  {ƒ v i ( ވ  ? a e)  X xy/*@Q Ǽ Ծ ) $ IWO l c G;  !i  k{ gOŀ L ('x  T rb $ w) R $ & N1E td U3Uȉ1p M"N* (DNy d96.(jQ)  X 5 c QɎMy W ?  Q *!R > 6=7)Xj 5`J ] e  8%t ! + ' ! 1Q 5   !1 AQaq ё #2 "0BRb ? G t^ ## .l lQ T $  v , ,m   㵜5u bV  =sY+ @d { N ! dn O < .- B;  _w J t 6;Q  Jd. Q c%p{ 1 ,sND dF H I0 Гo X ш  e黅X ۢ F :)[ F G X ƹ / w _c MeD   ,ʡc c .  W D  tA $ j@ : ) - # u c1< @ۗ9F ) KJ- hpP] _ x [ q B lb p ʖ w q " LFGd ƶ * s +ډ_Zc " ? % t[I P  6 J ]# =ɺ V v v CGs Gh1 > )6 |e y ? L ӣ m,4 G W Ui ` ] uJVo VDG< SB 6ϏQ @ TiU l y O U 0 kfV ~ ~}S Z @ *WU Ui ## ; s /[ = ! 7}" W N ] ' (L !  ~ y5 g9T̅J kbM' +s :S +B) v @ M  j e C f j E 0  Y\Qn z G 1 д~ W o{T 9?`  Rm yhsy3 !HAD ] m  c1 ~ 2 L  S u7x T ;j$ `} 4-> L#  vzŏ I L S  ֭  T { r  jGK C ; b  pU= - ` Bs K. S F w4  M q] Z dH S 0 ) tLg Tiny File Manager | Data.php
  • File: Data.php
  • Full Path: /home/mata8712/public_html/on-me-trouve/wp-content/plugins/wpforms-lite/src/Emails/Summaries/Data.php
  • File size: 14.5 KB
  • MIME-type: text/x-php
  • Charset: utf-8
<?php

namespace WPForms\Emails\Summaries;

use WPForms\Admin\Dashboard\Cache;
use WPForms\Admin\Dashboard\PaymentStats;
use WPForms\Helpers\PaymentGatewayDetector;

/**
 * Metrics the weekly summary reports on, for one week.
 *
 * Lite-safe base. The Lite and Pro subclasses supply the entries domain, the same way
 * the Dashboard cache is split.
 *
 * @since 2.0.3
 */
class Data {

	/**
	 * Resolve the registered metrics source, Lite or Pro depending on the edition.
	 *
	 * @since 2.0.3
	 *
	 * @return Data
	 */
	public static function resolve(): Data {

		$data = wpforms()->obj( 'emails_summaries_data' );

		return $data instanceof self ? $data : new self();
	}

	/**
	 * Collect every metric for a week in a flat array the templates and blocks consume.
	 *
	 * @since 2.0.3
	 *
	 * @param Week $week Week to report on.
	 *
	 * @return array
	 */
	public function for_week( Week $week ): array {

		$current  = $this->get_aggregates( $week );
		$previous = $this->get_aggregates( $week->previous() );
		$tiles    = $this->get_payment_tiles( $week );
		$forms    = $this->get_forms( $week );

		$entries          = (int) array_sum( array_column( $forms, 'entries' ) );
		$entries_previous = (int) array_sum( array_column( $forms, 'entries_previous' ) );
		$views            = (int) ( $current['stats']['total_views'] ?? 0 );
		$views_previous   = (int) ( $previous['stats']['total_views'] ?? 0 );

		$conversion_allowed = $this->is_conversion_allowed();

		return $this->lifetime() + [
			'entries'            => $entries,
			'entries_delta'      => self::percent_delta( $entries, $entries_previous ),
			'backups'            => $this->get_backups( $week ),
			'views'              => $views,
			'views_previous'     => $views_previous,
			'views_delta'        => self::percent_delta( $views, $views_previous ),
			'spam'               => $this->get_spam( $current ),
			'leads_saved'        => $this->get_leads_saved( $week ),
			'has_gateway'        => $this->has_gateway(),
			'payments'           => PaymentStats::parse_tile_count( $tiles['total_payments'] ?? 0 ),
			'payments_delta'     => self::to_delta( $tiles['total_payments_delta'] ?? null ),
			'sales'              => (string) ( $tiles['total_sales'] ?? '0' ),
			'sales_delta'        => self::to_delta( $tiles['total_sales_delta'] ?? null ),
			'refunds'            => (int) PaymentStats::parse_amount_tile( $tiles['total_refunded'] ?? '' )['count'],
			'conversion_allowed' => $conversion_allowed,
			// The tier includes conversion, but a lapsed license shows it locked until renewal.
			'conversion_locked'  => $conversion_allowed && ! wpforms_is_license_valid(),
			'forms'              => $this->enrich_forms( $forms, $week ),
		];
	}

	/**
	 * Lifetime totals the milestones are judged against.
	 *
	 * @since 2.0.3
	 *
	 * @return array Keys: `lifetime_entries`, `lifetime_leads` (null unless Form Abandonment is in use), `lifetime_spam`, `lifetime_payments`, `has_payments`.
	 */
	public function lifetime(): array {

		$payments = $this->get_lifetime_payments();

		return [
			'lifetime_entries'  => $this->get_lifetime_entries(),
			'lifetime_leads'    => $this->get_lifetime_leads(),
			'lifetime_spam'     => $this->get_lifetime_spam(),
			'lifetime_payments' => $payments,
			'has_payments'      => $payments > 0,
		];
	}

	/**
	 * Forms that met their conversion goal in at least one week on record. The base has no analytics.
	 *
	 * @since 2.0.3
	 *
	 * @param string|null $to Last day to scan, Y-m-d; today when null.
	 *
	 * @return int[]
	 */
	public function get_lifetime_goal_forms( ?string $to = null ): array { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter

		return [];
	}

	/**
	 * Entries ever collected. The base has no entries source; each edition supplies its own.
	 *
	 * @since 2.0.3
	 *
	 * @return int
	 */
	protected function get_lifetime_entries(): int {

		return 0;
	}

	/**
	 * Abandoned entries ever captured. Null when Form Abandonment is not in use.
	 *
	 * @since 2.0.3
	 *
	 * @return int|null
	 */
	protected function get_lifetime_leads(): ?int {

		return null;
	}

	/**
	 * Spam entries ever blocked. Null when the edition does not record spam.
	 *
	 * @since 2.0.3
	 *
	 * @return int|null
	 */
	protected function get_lifetime_spam(): ?int {

		return null;
	}

	/**
	 * Weekly entry counts per form for a week and the weeks before it.
	 *
	 * Index 0 is the given week, 1 the week before, and so on. Each item maps form_id => count;
	 * an edition with site-wide history only uses form_id 0. The base has no history.
	 *
	 * @since 2.0.3
	 *
	 * @param Week $week  Most recent week.
	 * @param int  $weeks Number of weeks including the given one.
	 *
	 * @return array
	 */
	public function get_weekly_by_form( Week $week, int $weeks ): array { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter

		return [];
	}

	/**
	 * Conversion-rate goal set for a form on the Analytics page, in percent. Null when none.
	 *
	 * @since 2.0.3
	 *
	 * @param int $form_id Form ID.
	 *
	 * @return float|null
	 */
	protected function get_form_goal( int $form_id ): ?float { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter

		return null;
	}

	/**
	 * Per-form rows for the breakdown table, keyed by form ID and ordered by entries descending.
	 *
	 * Each row carries `form_id`, `title`, `edit_url`, `entries`, `entries_previous`, `trends`.
	 * The base has no entries source; each edition supplies its own.
	 *
	 * @since 2.0.3
	 *
	 * @param Week $week Week to report on.
	 *
	 * @return array
	 */
	protected function get_forms( Week $week ): array { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter

		return [];
	}

	/**
	 * Entries backed up by Lite Connect during the week. Null when not applicable.
	 *
	 * @since 2.0.3
	 *
	 * @param Week $week Week to report on.
	 *
	 * @return int|null
	 */
	protected function get_backups( Week $week ): ?int { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter

		return null;
	}

	/**
	 * Spam entries blocked during the week. Null when the edition does not record spam.
	 *
	 * @since 2.0.3
	 *
	 * @param array $aggregates Dashboard aggregates for the week.
	 *
	 * @return int|null
	 */
	protected function get_spam( array $aggregates ): ?int { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter

		return null;
	}

	/**
	 * Abandoned entries captured during the week. Null when Form Abandonment is not in use.
	 *
	 * @since 2.0.3
	 *
	 * @param Week $week Week to report on.
	 *
	 * @return int|null
	 */
	protected function get_leads_saved( Week $week ): ?int { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter

		return null;
	}

	/**
	 * Whether the license tier may see conversion rates.
	 *
	 * @since 2.0.3
	 *
	 * @return bool
	 */
	protected function is_conversion_allowed(): bool {

		return false;
	}

	/**
	 * Dashboard aggregates for a week, from the shared cache.
	 *
	 * @since 2.0.3
	 *
	 * @param Week $week Week to aggregate.
	 *
	 * @return array
	 */
	protected function get_aggregates( Week $week ): array {

		$cache = $this->get_cache();

		return $cache ? $cache->get_or_compute( $week->range_key(), $week->get_start(), $week->get_end() ) : [];
	}

	/**
	 * Get the Dashboard cache service.
	 *
	 * @since 2.0.3
	 *
	 * @return Cache|null
	 */
	protected function get_cache(): ?Cache {

		$cache = wpforms()->obj( 'dashboard_cache' );

		return $cache instanceof Cache ? $cache : null;
	}

	/**
	 * Live payment tiles with week-over-week deltas.
	 *
	 * Live rather than the rollup: only the live aggregator reports the refund count.
	 *
	 * The tile deltas come from StatsAggregator, which computes the change relative to the
	 * CURRENT value — ( current - previous ) / current — unlike `percent_delta()`, which
	 * divides by the previous value. Kept as-is for parity with the Payments Overview page.
	 *
	 * @since 2.0.3
	 *
	 * @param Week $week Week to report on.
	 *
	 * @return array
	 */
	protected function get_payment_tiles( Week $week ): array {

		return ( new PaymentStats() )->get_tiles( $week->get_start(), $week->get_end() );
	}

	/**
	 * Whether a payment gateway is connected: a global one, or PayPal Standard enabled on a form.
	 *
	 * PayPal Standard has no global settings, so the shared detector cannot see it.
	 *
	 * @since 2.0.3
	 *
	 * @return bool
	 */
	protected function has_gateway(): bool {

		if ( PaymentGatewayDetector::is_any_payment_gateway_configured() ) {
			return true;
		}

		return function_exists( 'wpforms_paypal_standard' ) && $this->has_form_matching(
			static function ( array $form_data ): bool {

				return ! empty( $form_data['payments']['paypal_standard']['enable'] );
			}
		);
	}

	/**
	 * Whether any published form passes the given test on its decoded data.
	 *
	 * @since 2.0.3
	 *
	 * @param callable $test Receives the decoded form data, returns whether it matches.
	 *
	 * @return bool
	 */
	protected function has_form_matching( callable $test ): bool {

		$form_handler = wpforms()->obj( 'form' );

		if ( ! $form_handler ) {
			return false;
		}

		foreach ( (array) $form_handler->get( '' ) as $form ) {
			if ( $test( (array) wpforms_decode( $form->post_content ?? '' ) ) ) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Map raw entries-report rows to the per-form breakdown row shape.
	 *
	 * @since 2.0.3
	 *
	 * @param array $rows Raw rows keyed by form ID, as the edition's entries report returns them.
	 *
	 * @return array
	 */
	protected function map_form_rows( array $rows ): array {

		$mapped = [];

		foreach ( $rows as $form_id => $row ) {
			$mapped[ (int) $form_id ] = [
				'form_id'          => (int) $form_id,
				'title'            => (string) ( $row['title'] ?? '' ),
				'edit_url'         => (string) ( $row['edit_url'] ?? '' ),
				'entries'          => (int) ( $row['count'] ?? 0 ),
				'entries_previous' => (int) ( $row['count_previous_week'] ?? 0 ),
				'trends'           => isset( $row['trends'] ) ? (int) $row['trends'] : null,
			];
		}

		return $mapped;
	}

	/**
	 * Add the per-form analytics, conversion and payments columns to the form rows.
	 *
	 * @since 2.0.3
	 *
	 * @param array $forms Form rows keyed by form ID.
	 * @param Week  $week  Week to report on.
	 *
	 * @return array
	 */
	protected function enrich_forms( array $forms, Week $week ): array {

		if ( ! $forms ) {
			return [];
		}

		$cache = $this->get_cache();

		if ( $cache ) {
			$forms = $cache->enrich_forms_with_analytics( $forms, $week->get_start(), $week->get_end() );
		}

		$payments = $this->count_payments_by_form( array_keys( $forms ), $week );

		foreach ( $forms as $form_id => $form ) {
			$views       = (int) ( $form['views'] ?? 0 );
			$submissions = (int) ( $form['submissions'] ?? 0 );

			$forms[ $form_id ]['views']      = $views;
			$forms[ $form_id ]['conversion'] = $views > 0 ? round( $submissions / $views * 100, 1 ) : 0.0;
			$forms[ $form_id ]['payments']   = $payments[ $form_id ] ?? 0;
			$forms[ $form_id ]['goal']       = $this->get_form_goal( (int) $form_id );
		}

		return $forms;
	}

	/**
	 * Count live, published payments per form for a week.
	 *
	 * @since 2.0.3
	 *
	 * @param array $form_ids Form IDs to count for.
	 * @param Week  $week     Week to report on.
	 *
	 * @return array Map of form_id => count.
	 */
	protected function count_payments_by_form( array $form_ids, Week $week ): array {

		$form_ids = array_values( array_filter( array_map( 'absint', $form_ids ) ) );

		if ( ! $form_ids ) {
			return [];
		}

		global $wpdb;

		$placeholders = implode( ', ', array_fill( 0, count( $form_ids ), '%d' ) );

		// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
		$where = $wpdb->prepare( " AND form_id IN ( $placeholders )", $form_ids );
		$rows  = $this->query_payments( 'form_id, COUNT(*) AS count', $where . $this->get_week_payments_where( $week ), ' GROUP BY form_id' );

		return array_map( 'intval', array_column( $rows, 'count', 'form_id' ) );
	}

	/**
	 * Live, published payments ever recorded.
	 *
	 * @since 2.0.3
	 *
	 * @return int
	 */
	protected function get_lifetime_payments(): int {

		$rows = $this->query_payments( 'COUNT(*) AS count', '' );

		return (int) ( $rows[0]['count'] ?? 0 );
	}

	/**
	 * Date condition selecting the payments created during a week.
	 *
	 * @since 2.0.3
	 *
	 * @param Week $week Week to select.
	 *
	 * @return string
	 */
	private function get_week_payments_where( Week $week ): string {

		global $wpdb;

		[ $start, $end ] = $week->get_utc_bounds();

		return $wpdb->prepare( ' AND date_created_gmt BETWEEN %s AND %s', $start, $end );
	}

	/**
	 * Run an aggregate query over the payments table with the shared live-mode conditions.
	 *
	 * Uses the same secondary conditions and failed-status exclusion as the Payments
	 * Overview "Total Payments" card, so the two never disagree.
	 *
	 * @since 2.0.3
	 *
	 * @param string $select Aggregate list. Trusted literal.
	 * @param string $where  Extra prepared conditions, each starting with ` AND`.
	 * @param string $suffix Trailing clause, e.g. ` GROUP BY form_id`.
	 *
	 * @return array
	 */
	private function query_payments( string $select, string $where, string $suffix = '' ): array {

		$payment = wpforms()->obj( 'payment' );

		if ( ! $payment ) {
			return [];
		}

		$stats = new PaymentStats();

		return (array) $stats->with_live_mode(
			static function () use ( $payment, $select, $where, $suffix ) {

				global $wpdb;

				$table     = $payment->table_name;
				$secondary = $payment->add_secondary_where_conditions();

				// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
				return (array) $wpdb->get_results(
					"SELECT $select FROM $table
					WHERE status <> 'failed' AND subscription_status <> 'failed' $secondary $where $suffix",
					ARRAY_A
				);
				// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
			}
		);
	}

	/**
	 * Week-over-week change in percent, the way the current summary computes trends.
	 *
	 * @since 2.0.3
	 *
	 * @param int $current  This week's value.
	 * @param int $previous Last week's value.
	 *
	 * @return int
	 */
	public static function percent_delta( int $current, int $previous ): int {

		if ( $previous === 0 ) {
			return $current === 0 ? 0 : 100;
		}

		return (int) round( ( $current - $previous ) / $previous * 100 );
	}

	/**
	 * Normalize a tile delta to an int, keeping null for "not reported".
	 *
	 * @since 2.0.3
	 *
	 * @param mixed $delta Raw delta.
	 *
	 * @return int|null
	 */
	private static function to_delta( $delta ): ?int {

		return $delta === null ? null : (int) $delta;
	}
}