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 | Analytics.php
  • File: Analytics.php
  • Full Path: /home/mata8712/public_html/on-me-trouve/wp-content/plugins/wpforms-lite/src/Admin/Forms/Analytics.php
  • File size: 14.04 KB
  • MIME-type: text/x-php
  • Charset: utf-8
<?php

namespace WPForms\Admin\Forms;

use WP_Post;
use WPForms\Admin\Forms\Table\Facades\Columns;
use WPForms\Analytics\Analytics as AnalyticsFeature;
use WPForms\Pro\Analytics\Analytics as ProAnalytics;

/**
 * Analytics columns on the Forms Overview page.
 *
 * Registers three new columns (Views, Interactions, Conversion), pre-fetches
 * their data via a bulk query, and renders cells.
 *
 * @since 2.0.0
 */
class Analytics {

	/**
	 * Pre-fetched stats keyed by form ID.
	 *
	 * @since 2.0.0
	 *
	 * @var array
	 */
	private $stats = [];

	/**
	 * Start the engine.
	 *
	 * @since 2.0.0
	 */
	public function init(): void {

		$this->hooks();
	}

	/**
	 * Register WordPress hooks.
	 *
	 * @since 2.0.0
	 */
	private function hooks(): void {

		add_filter( 'wpforms_admin_forms_table_facades_columns_data', [ $this, 'register_columns' ] );
		add_filter( 'wpforms_overview_table_column_value', [ $this, 'render_column' ], 10, 3 );
		add_action( 'wpforms_admin_forms_list_table_prepare_items_after', [ $this, 'prefetch_stats' ] );
		add_filter( 'default_hidden_columns', [ $this, 'set_column_defaults' ], 11, 2 );
		add_action( 'wpforms_admin_header_after', [ $this, 'render_feature_tooltip' ] );

		// Priority 10 runs after the core row actions (priority 9), so "Entries" is
		// already present to insert "Analytics" after.
		add_filter( 'wpforms_overview_row_actions', [ $this, 'add_analytics_row_action' ], 10, 2 );
	}

	/**
	 * Add an "Analytics" row action after "Entries" in the Forms Overview table.
	 *
	 * Pro/Elite only — Lite users see the upgrade badge in the analytics columns
	 * instead. Gated by the same per-form capability as the analytics page.
	 *
	 * @since 2.0.0
	 *
	 * @param array|mixed $row_actions Row action links keyed by slug.
	 * @param WP_Post     $form        Form object.
	 *
	 * @return array
	 */
	public function add_analytics_row_action( $row_actions, $form ): array {

		$row_actions = (array) $row_actions;

		if ( ! wpforms()->is_pro() || ! ProAnalytics::is_allowed() || ! wpforms_current_user_can( 'view_entries_form_single', $form->ID ) ) {
			return $row_actions;
		}

		$analytics = [
			'analytics' => sprintf(
				'<a href="%s" title="%s">%s</a>',
				esc_url(
					add_query_arg(
						[
							'page'    => 'wpforms-analytics',
							'form_id' => $form->ID,
						],
						admin_url( 'admin.php' )
					)
				),
				esc_attr__( 'View analytics', 'wpforms-lite' ),
				esc_html__( 'Analytics', 'wpforms-lite' )
			),
		];

		// Insert after Entries (or append when absent), mirroring register_columns().
		$position = array_search( 'entries', array_keys( $row_actions ), true );
		$position = $position !== false ? $position + 1 : count( $row_actions );

		return array_slice( $row_actions, 0, $position, true )
			+ $analytics
			+ array_slice( $row_actions, $position, null, true );
	}

	/**
	 * Register analytics columns (Views, Interactions, Conversion).
	 *
	 * Inserts three analytics columns immediately after the Entries column when
	 * it exists, or appends them at the end of the columns list otherwise.
	 *
	 * Hooks: wpforms_admin_forms_table_facades_columns_data (filter).
	 *
	 * @since 2.0.0
	 *
	 * @param array|mixed $columns Existing columns data.
	 *
	 * @return array
	 */
	public function register_columns( $columns ): array {

		$columns = (array) $columns;

		$analytics_columns = [
			'analytics_views'        => [
				'label' => esc_html__( 'Views', 'wpforms-lite' ),
			],
			'analytics_interactions' => [
				'label' => esc_html__( 'Interactions', 'wpforms-lite' ),
			],
			'analytics_conversion'   => [
				'label' => esc_html__( 'Conversion', 'wpforms-lite' ),
			],
		];

		$position = array_search( 'entries', array_keys( $columns ), true );
		$position = $position !== false ? $position + 1 : count( $columns );

		return array_slice( $columns, 0, $position, true )
			+ $analytics_columns
			+ array_slice( $columns, $position, null, true );
	}

	/**
	 * Render an analytics column cell.
	 *
	 * Dispatches to the appropriate private renderer based on $column_name.
	 * Returns the original $value unchanged when $form is not a WP_Post instance
	 * or when $column_name does not match an analytics column.
	 *
	 * Hooks: wpforms_overview_table_column_value (filter).
	 * Matches the signature used by Forms\Locator::column_value — no strict type
	 * hints on filter callback params.
	 *
	 * @since 2.0.0
	 *
	 * @param mixed  $value       Current cell value (pass-through for unknown columns).
	 * @param mixed  $form        Form post object; must be WP_Post to render.
	 * @param string $column_name Column identifier.
	 *
	 * @return mixed
	 */
	public function render_column( $value, $form, string $column_name ) {

		if ( ! $form instanceof WP_Post ) {
			return $value;
		}

		$form_id = (int) $form->ID;

		switch ( $column_name ) {
			case 'analytics_views':
				$value = $this->render_views( $form_id );
				break;

			case 'analytics_interactions':
				$value = $this->render_interactions( $form_id );
				break;

			case 'analytics_conversion':
				$value = $this->render_conversion( $form_id );
				break;
		}

		return $value;
	}

	/**
	 * Bulk-fetch stats for all forms on the current overview page.
	 *
	 * Retrieves views and submissions for every form in a single DB call and
	 * stores the result in $stats keyed by form ID. No-ops when $form_ids is
	 * empty or non-array, or when the analytics DB object is unavailable.
	 *
	 * Hooks: wpforms_admin_forms_list_table_prepare_items_after (action). The
	 * parameter is untyped because the action's payload depends on the dispatcher
	 * (`ListTable::prepare_items()`), which may pass a non-array if the items
	 * lookup degraded - keeping this method tolerant prevents a fatal in that path.
	 *
	 * @since 2.0.0
	 *
	 * @param mixed $form_ids Form IDs (expected: int[]).
	 */
	public function prefetch_stats( $form_ids ): void {

		if ( ! is_array( $form_ids ) || empty( $form_ids ) ) {
			$this->stats = [];

			return;
		}

		$db = wpforms()->obj( 'analytics_db' );

		if ( ! $db ) {
			$this->stats = [];

			return;
		}

		$this->stats = $db->get_overview_stats( $form_ids );
	}

	/**
	 * Hide the Shortcode column by default on the Forms Overview screen.
	 *
	 * Hooks: default_hidden_columns (filter, priority 11). Runs after
	 * ListTable::default_hidden_columns() (priority 10), receives its output,
	 * gates on the same screen ID + Columns::has_selected_columns() conditions,
	 * and appends 'shortcode' to keep the analytics columns visually grouped
	 * after Entries on first visit.
	 *
	 * @since 2.0.0
	 *
	 * @param mixed $hidden Hidden columns array (filter input may be non-array).
	 * @param mixed $screen WP_Screen-like object for the current admin screen.
	 *
	 * @return array
	 */
	public function set_column_defaults( $hidden, $screen ): array {

		$hidden = (array) $hidden;

		if ( ! is_object( $screen ) || ! isset( $screen->id ) || $screen->id !== 'toplevel_page_wpforms-overview' ) {
			return $hidden;
		}

		if ( Columns::has_selected_columns() ) {
			return $hidden;
		}

		$hidden[] = 'shortcode';

		return $hidden;
	}

	/**
	 * Render the Views column cell.
	 *
	 * @since 2.0.0
	 *
	 * @param int $form_id Form ID.
	 *
	 * @return string
	 */
	private function render_views( int $form_id ): string {

		$views = $this->stats[ $form_id ]['views'] ?? null;

		if ( $views === null ) {
			return '—';
		}

		$value = number_format_i18n( (int) $views );

		if ( ! wpforms()->is_pro() ) {
			return $value;
		}

		if ( ! ProAnalytics::is_allowed() ) {
			return $value;
		}

		return $this->build_analytics_link( $form_id, 'views', $value );
	}

	/**
	 * Render the Interactions column cell.
	 *
	 * On Lite, returns the Pro upgrade badge. On Pro with stats present,
	 * returns a link to the Analytics page for the given form.
	 * Returns a dash on Pro when the form has no stats.
	 *
	 * @since 2.0.0
	 *
	 * @param int $form_id Form ID.
	 *
	 * @return string
	 */
	private function render_interactions( int $form_id ): string {

		if ( ! wpforms()->is_pro() || ! ProAnalytics::is_allowed() ) {
			return $this->render_pro_badge( __( 'Upgrade to Pro to unlock Interactions', 'wpforms-lite' ) );
		}

		$interactions = $this->stats[ $form_id ]['interactions'] ?? null;

		if ( $interactions === null ) {
			return '—';
		}

		return $this->build_analytics_link( $form_id, 'interactions', number_format_i18n( (int) $interactions ) );
	}

	/**
	 * Render the Pro upgrade teaser for locked columns (Lite only).
	 *
	 * Outputs a blurred-value image wrapped in the education-modal link, so the
	 * locked metric reads as "there is data here, upgrade to see it". Both gated
	 * columns (Interactions, Conversion) share a single `analytics-upgrade`
	 * utm_content value. The `data-utm-medium` attribute is read by the Lite
	 * education modal JS to override the upgrade URL's utm_medium with
	 * `forms-overview`.
	 *
	 * @since 2.0.0
	 *
	 * @param string $tooltip Hover title naming the locked metric (e.g. "Upgrade to Pro to unlock Interactions").
	 *
	 * @return string
	 */
	private function render_pro_badge( string $tooltip ): string {

		return sprintf(
			'<a href="#" class="education-modal" title="%5$s" data-action="upgrade" data-name="%1$s" data-license="pro" data-banner-src="%2$s" data-utm-medium="forms-overview" data-utm-content="analytics-upgrade"><img src="%3$s" alt="%4$s" width="48" height="27"></a>',
			esc_attr__( 'Form Analytics', 'wpforms-lite' ),
			esc_url( WPFORMS_PLUGIN_URL . 'assets/images/education/analytics-preview.png' ),
			esc_url( WPFORMS_PLUGIN_URL . 'assets/images/education/blurred-value.svg' ),
			esc_attr__( 'Upgrade to Pro', 'wpforms-lite' ),
			esc_attr( $tooltip )
		);
	}

	/**
	 * Render the Conversion column cell.
	 *
	 * @since 2.0.0
	 *
	 * @param int $form_id Form ID.
	 *
	 * @return string
	 */
	private function render_conversion( int $form_id ): string {

		if ( ! wpforms()->is_pro() || ! ProAnalytics::is_allowed() ) {
			return $this->render_pro_badge( __( 'Upgrade to Pro to unlock Conversion Rate', 'wpforms-lite' ) );
		}

		$value = $this->calculate_conversion_value( $this->stats[ $form_id ] ?? null );

		if ( $value === null ) {
			return '—';
		}

		return $this->build_analytics_link( $form_id, 'conversion', $value );
	}

	/**
	 * Compute the formatted conversion-rate string for a form's stats.
	 *
	 * @since 2.0.0
	 *
	 * @param array|null $stats Pre-fetched stats for the form, or null when absent.
	 *
	 * @return string|null Formatted "NN.N%" value, or null when stats are missing or views are zero.
	 */
	private function calculate_conversion_value( ?array $stats ): ?string {

		if ( ! $stats ) {
			return null;
		}

		$views       = (int) ( $stats['views'] ?? 0 );
		$submissions = (int) ( $stats['submissions'] ?? 0 );

		if ( $views <= 0 ) {
			return null;
		}

		$rate = round( ( $submissions / $views ) * 100, 1 );

		return number_format_i18n( $rate, 1 ) . '%';
	}

	/**
	 * Build the Analytics-page link markup for a populated column cell (Pro).
	 *
	 * @since 2.0.0
	 *
	 * @param int    $form_id      Form ID the link targets.
	 * @param string $css_modifier Column suffix for the link CSS class.
	 * @param string $value        Display value (escaped on output).
	 *
	 * @return string Anchor markup pointing at the form's Analytics page.
	 */
	private function build_analytics_link( int $form_id, string $css_modifier, string $value ): string {

		return sprintf(
			'<a href="%1$s" class="wpforms-analytics-%2$s-link">%3$s</a>',
			esc_url( admin_url( 'admin.php?page=wpforms-analytics&form_id=' . $form_id ) ),
			esc_attr( $css_modifier ),
			esc_html( $value )
		);
	}

	/**
	 * Render the Form Analytics feature-discovery tooltip on Forms Overview.
	 *
	 * @since 2.0.0
	 */
	public function render_feature_tooltip(): void {

		if ( ! $this->should_render_feature_tooltip() ) {
			return;
		}

		$tooltip = wpforms()->obj( 'education_feature_tooltip' );

		if ( ! $tooltip ) {
			return;
		}

		/**
		 * Filter the Learn More URL for the Form Analytics feature-discovery tooltip.
		 *
		 * @since 2.0.0
		 *
		 * @param string $url Default UTM-wrapped wpforms.com URL.
		 */
		$learn_more_url = apply_filters(
			'wpforms_admin_forms_analytics_render_feature_tooltip_learn_more_url',
			wpforms_utm_link(
				AnalyticsFeature::DOC_URL,
				'forms-overview',
				'Analytics Feature Tooltip'
			)
		);

		$button = wpforms()->is_pro() && ProAnalytics::is_allowed()
			? [
				'text'    => esc_html__( 'View Analytics', 'wpforms-lite' ),
				'url'     => add_query_arg( 'page', 'wpforms-analytics', admin_url( 'admin.php' ) ),
				'classes' => 'wpforms-btn wpforms-btn-sm wpforms-btn-orange wpforms-education-feature-tooltip-view',
			]
			: [
				'text'    => esc_html__( 'Upgrade to Pro', 'wpforms-lite' ),
				'url'     => '#',
				'classes' => 'wpforms-btn wpforms-btn-sm wpforms-btn-orange education-modal',
				'data'    => [
					'name'        => esc_attr__( 'Form Analytics', 'wpforms-lite' ),
					'license'     => 'pro',
					'action'      => 'upgrade',
					'banner-src'  => WPFORMS_PLUGIN_URL . 'assets/images/education/analytics-preview.png',
					'utm-medium'  => 'forms-overview',
					'utm-content' => 'analytics-feature-tooltip',
				],
			];

		$tooltip->render(
			[
				'title'          => esc_html__( 'Form Analytics', 'wpforms-lite' ),
				'badge'          => esc_html__( 'NEW', 'wpforms-lite' ),
				'text'           => esc_html__( 'Monitor views, conversions, and field-level activity to optimize performance.', 'wpforms-lite' ),
				'section'        => 'analytics-feature-discovery',
				'learn_more_url' => $learn_more_url,
				'button'         => $button,
			]
		);
	}

	/**
	 * Whether the Form Analytics feature-discovery tooltip should render.
	 *
	 * Gates on the current admin screen and the per-user dismissal flag stored
	 * in the `wpforms_dismissed` user meta map.
	 *
	 * @since 2.0.0
	 *
	 * @return bool True when the tooltip should render on this request.
	 */
	private function should_render_feature_tooltip(): bool {

		$screen    = get_current_screen();
		$screen_id = $screen->id ?? '';

		if ( ! $screen || $screen_id !== 'toplevel_page_wpforms-overview' ) {
			return false;
		}

		$dismissed = (array) get_user_meta( get_current_user_id(), 'wpforms_dismissed', true );

		return empty( $dismissed['edu-analytics-feature-discovery'] );
	}
}