PluginProbe
WowOptin: Next-Gen Popup Maker – Create Stunning Popups and Optins for Lead Generation / trunk
WowOptin: Next-Gen Popup Maker – Create Stunning Popups and Optins for Lead Generation vtrunk
1.4.48 1.4.47 1.4.46 1.4.45 1.4.44 1.4.43 1.4.42 1.4.41 1.4.40 1.4.39 1.4.38 1.4.37 1.4.36 1.1.2 1.2.0 1.2.1 1.2.2 1.3.0 1.3.1 1.4.0 1.4.1 1.4.10 1.4.11 1.4.12 1.4.13 All 64 releases
optin / includes / class-ab-testing.php

class-ab-testing.php in WowOptin: Next-Gen Popup Maker – Create Stunning Popups and Optins for Lead Generation trunk, at includes/class-ab-testing.php

858 lines 20.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php // phpcs:ignore
2
3 namespace OPTN\Includes;
4
5 use OPTN\Includes\Dto\CreateAbTestDto;
6 use OPTN\Includes\Dto\UpdateAbTestDto;
7 use OPTN\Includes\Utils\Utils;
8
9 /**
10 * AB Testing
11 */
12 class AbTesting {
13
14 /**
15 * Unique prefix for our cron hooks to avoid collisions.
16 */
17 const CRON_HOOK_PREFIX = 'optn_abt_';
18
19 /**
20 * The specific cron hook for winner selection.
21 */
22 const WINNER_SELECTION_HOOK = self::CRON_HOOK_PREFIX . 'select_winner';
23
24 const DRAFT_STATUS = 0;
25 const ACTIVE_STATUS = 1;
26 const END_STATUS = 2;
27
28 /**
29 * Map of status values to their integer representations.
30 *
31 * @var array
32 */
33 public static $status_map = array(
34 'draft' => self::DRAFT_STATUS,
35 'active' => self::ACTIVE_STATUS,
36 'end' => self::END_STATUS,
37 );
38
39 /**
40 * Map of status values to their integer representations.
41 *
42 * @var array
43 */
44 private static $status_map_flip;
45
46 /**
47 * Map of type values to their integer representations.
48 *
49 * @var array
50 */
51 private static $type_map = array(
52 'manual' => 0,
53 'automatic' => 1,
54 );
55
56 /**
57 * Map of type values to their integer representations.
58 *
59 * @var array
60 */
61 private static $type_map_flip;
62
63 /**
64 * Map of metrics to their integer representations.
65 *
66 * @var array
67 */
68 private static $metrics_map = array(
69 'conv' => 0,
70 'cr' => 1,
71 'leads' => 2,
72 'rev' => 3,
73 );
74
75 /**
76 * Db
77 *
78 * @var \OPTN\Includes\Db
79 */
80 private $db;
81
82 /**
83 * Map of metrics to their integer representations.
84 *
85 * @var array
86 */
87 private static $metrics_map_flip;
88
89 /**
90 * Constructor. Sets up the WordPress action for our cron hook.
91 */
92 public function __construct() {
93
94 $this->db = Db::get_instance();
95
96 add_action( self::WINNER_SELECTION_HOOK, array( $this, 'handle_scheduled_winner_selection' ), 10, 1 );
97
98 add_action( 'optn_abt_created', array( $this, 'test_created' ), 10, 2 );
99 add_action( 'optn_abt_updated', array( $this, 'test_updated' ), 10, 1 );
100 add_action( 'optn_abt_before_delete', array( $this, 'test_deleted' ), 10, 1 );
101 }
102
103 /**
104 * Action for when a test is created.
105 *
106 * @param int $id Test ID.
107 * @param CreateAbTestDto $dto Test data.
108 * @return void
109 */
110 public function test_created( $id, CreateAbTestDto $dto ) {
111 if ( 'automatic' === $dto->type && 'active' === $dto->status ) {
112 $this->schedule_winner_selection( $id, $dto->started_at, $dto->duration );
113 }
114
115 $this->sync_variant_view_history( $id, $dto->status );
116 }
117
118 /**
119 * Action for when a test is updated.
120 *
121 * @param UpdateAbTestDto $dto Test data.
122 * @return void
123 */
124 public function test_updated( UpdateAbTestDto $dto ) {
125 // Reschedule if duration is updated.
126 if (
127 'automatic' === $dto->type &&
128 $dto->update_duration &&
129 'active' === $dto->status
130 ) {
131 $this->schedule_winner_selection( $dto->id, $dto->started_at, $dto->duration );
132 } elseif ( 'manual' === $dto->type ) {
133 // Unschedule if type is changed to manual.
134 $this->unschedule_winner_selection( $dto->id );
135 }
136
137 // Reset sticky assignments so updated tests can rebalance traffic.
138 $this->sync_variant_view_history( $dto->id, $dto->status );
139 }
140
141 /**
142 * Action for when a test is deleted.
143 *
144 * @param int $id Test ID.
145 * @return void
146 */
147 public function test_deleted( $id ) {
148 $this->unschedule_winner_selection( $id );
149 self::delete_variant_view_history( $id );
150 }
151
152 /**
153 * Sync variant view history with the current test status.
154 *
155 * @param int $test_id Test ID.
156 * @param string $status Test status.
157 * @return void
158 */
159 private function sync_variant_view_history( $test_id, $status ) {
160 self::delete_variant_view_history( $test_id );
161
162 if ( 'active' === $status ) {
163 self::create_variant_view_history( $test_id );
164 }
165 }
166
167 /**
168 * Handles manual winner selection.
169 *
170 * @param int $test_id A/B Test id.
171 * @param int $optin_id optin id.
172 * @return boolean True on success, false on failure.
173 */
174 public static function handle_manual_winner_selection( int $test_id, int $optin_id ) {
175 /**
176 * DB Instance
177 *
178 * @var \OPTN\Includes\Db $db
179 */
180 $db = Db::get_instance();
181
182 $abtest = $db->get_ab_test( $test_id );
183
184 // Do nothing if the test does not exist.
185 if ( empty( $abtest ) ) {
186 return false;
187 }
188
189 // Do nothing if the test is not active.
190 if ( ! isset( $abtest['status'] ) || 'active' !== $abtest['status'] ) {
191 return false;
192 }
193
194 // Do nothing if the optins list is empty.
195 if ( ! isset( $abtest['optins'] ) || count( $abtest['optins'] ) < 1 ) {
196 return false;
197 }
198
199 $optin_ids = array_map(
200 function ( $optin ) {
201 return $optin['id'];
202 },
203 $abtest['optins']
204 );
205
206 if ( ! in_array( $optin_id, $optin_ids ) ) { // phpcs:ignore
207 return false;
208 }
209
210 self::handle_end_actions( (int) $abtest['id'], (int) $optin_id, $optin_ids );
211
212 return true;
213 }
214
215 /**
216 * Handles End action after a winner has been selected.
217 *
218 * @param int $test_id A/B Test id.
219 * @param int $winner_id winner optin id.
220 * @param array $optin_ids all optin ids in the test.
221 */
222 public static function handle_end_actions( $test_id, $winner_id, $optin_ids ) {
223 /**
224 * DB Instance
225 *
226 * @var \OPTN\Includes\Db $db
227 */
228 $db = Db::get_instance();
229
230 global $wpdb;
231
232 // Enabling winner optin and removing it from trash.
233 $wpdb->update(
234 $db->optins_table,
235 array(
236 'enabled' => true,
237 'deleted_at' => null,
238 ),
239 array( 'id' => $winner_id )
240 );
241
242 // Disable loser optins, but not the ones that are in other ACTIVE A/B tests.
243 $loser_ids = array_filter(
244 $optin_ids,
245 function ( $id ) use ( $winner_id ) {
246 return intval( $id ) !== $winner_id;
247 }
248 );
249
250 $loser_ids_str = implode( ',', $loser_ids );
251
252 $draft_sql =
253 "UPDATE {$db->optins_table} c
254 LEFT JOIN {$db->ab_test_variants_table} v
255 ON v.optin_id = c.id AND v.test_id != {$test_id}
256 LEFT JOIN {$db->ab_tests_table} t
257 ON t.id = v.test_id
258 SET c.enabled = 0
259 WHERE
260 c.id IN ( {$loser_ids_str} ) AND
261 (
262 v.id IS NULL OR
263 t.status != " . self::ACTIVE_STATUS . '
264 )';
265
266 $wpdb->query( $draft_sql ); // phpcs:ignore
267
268 // Generating stats and saving them.
269 $res = $db->get_optin_stats_by_ids( $optin_ids );
270 $res['winner_id'] = $winner_id;
271 $res['optin_ids'] = $optin_ids;
272
273 $wpdb->update(
274 $db->ab_tests_table,
275 array(
276 'status' => self::END_STATUS,
277 'result' => wp_json_encode( $res ),
278 ),
279 array( 'id' => $test_id )
280 );
281 }
282
283 /**
284 * Schedules the winner selection event for a specific A/B test.
285 *
286 * @param int $test_id The ID of the A/B test.
287 * @param string $start_date The start date of the test.
288 * @param int $duration_days The number of days after which the winner should be selected.
289 * @return bool True on success, false on failure.
290 */
291 public function schedule_winner_selection( $test_id, $start_date, $duration_days ) {
292 if ( ! is_numeric( $test_id ) || $test_id <= 0 ) {
293 return false;
294 }
295
296 if ( ! is_numeric( $duration_days ) || $duration_days <= 0 ) {
297 return false;
298 }
299
300 $this->unschedule_winner_selection( $test_id );
301
302 $timestamp = strtotime( "+{$duration_days} days", strtotime( $start_date ) );
303 $args = array( 'test_id' => (int) $test_id );
304
305 return true === wp_schedule_single_event( $timestamp, self::WINNER_SELECTION_HOOK, $args );
306 }
307
308 /**
309 * Unschedules the winner selection event for a specific A/B test.
310 * Useful if a test is manually stopped, deleted, or its duration is changed.
311 *
312 * @param int $test_id The ID of the A/B test.
313 */
314 public function unschedule_winner_selection( $test_id ) {
315 if ( ! is_numeric( $test_id ) || $test_id <= 0 ) {
316 return;
317 }
318 $args = array( 'test_id' => (int) $test_id );
319 wp_clear_scheduled_hook( self::WINNER_SELECTION_HOOK, $args );
320 }
321
322 /**
323 * Handles the cron event when it's time to select a winner.
324 * This is the callback function for the WP-Cron job.
325 *
326 * @param int $test_id A/B Test id.
327 */
328 public function handle_scheduled_winner_selection( int $test_id ) {
329 if ( ! is_numeric( $test_id ) ) {
330 return;
331 }
332
333 $test_id = (int) $test_id;
334
335 $abtest = $this->db->get_ab_test( $test_id );
336
337 // Do nothing if the test does not exist.
338 if ( empty( $abtest ) ) {
339 return;
340 }
341
342 // Do nothing if the test is not active.
343 if ( ! isset( $abtest['status'] ) || 'active' !== $abtest['status'] ) {
344 return;
345 }
346
347 // Do nothing if the test is not automatic.
348 if ( ! isset( $abtest['type'] ) || 'automatic' !== $abtest['type'] ) {
349 return;
350 }
351
352 // Do nothing if the optins list is empty.
353 if ( ! isset( $abtest['optins'] ) || count( $abtest['optins'] ) < 1 ) {
354 return;
355 }
356
357 $this->perform_winner_selection_logic( $abtest );
358 }
359
360 /**
361 * Winner selection logic.
362 *
363 * @param array $abtest data.
364 */
365 private function perform_winner_selection_logic( $abtest ) {
366 $ids = array_map(
367 function ( $optin ) {
368 return intval( $optin['id'] );
369 },
370 $abtest['optins']
371 );
372
373 sort( $ids );
374
375 $winner_id = null;
376
377 // Determining winner.
378 switch ( $abtest['metric'] ) {
379 case 'leads':
380 $winner_id = $this->get_optin_id_by_leads( $ids );
381 break;
382 case 'cr':
383 $winner_id = $this->get_optin_id_by_cr( $ids );
384 break;
385 case 'conv':
386 $winner_id = $this->get_optin_id_by_conversions( $ids );
387 break;
388 case 'rev':
389 $winner_id = $this->get_optin_id_by_revenue( $ids );
390 break;
391 }
392
393 if ( empty( $winner_id ) ) {
394 $winner_id = $ids[0];
395 }
396
397 self::handle_end_actions( (int) $abtest['id'], (int) $winner_id, $ids );
398 }
399
400 /**
401 * Get the ID of the optin with highest total revenue for a given set of optins.
402 *
403 * @param array $ids optin ids.
404 * @return int
405 */
406 private function get_optin_id_by_revenue( $ids ) {
407 global $wpdb;
408
409 $ids_str = implode( ',', $ids );
410
411 $sql =
412 "SELECT
413 i.conv_id as `id`,
414 i.order_type,
415 i.order_id
416 FROM {$this->db->interactions_table} i
417 WHERE
418 i.conv_id IN ( {$ids_str} ) AND
419 i.order_id IS NOT NULL AND
420 i.order_type IS NOT NULL";
421
422 $data = $wpdb->get_results( $sql ); // phpcs:ignore
423
424 $winner_id = null;
425 $max_revenue = PHP_INT_MIN;
426
427 foreach ( $data as $d ) {
428 $rev = Utils::get_revenue( $d->order_type, $d->order_id );
429 if ( $max_revenue < $rev ) {
430 $max_revenue = $rev;
431 $winner_id = $d->id;
432 }
433 }
434
435 return intval( $winner_id );
436 }
437
438 /**
439 * Get the ID of the optin with highest total conversions for a given set of optins.
440 *
441 * @param array $ids optin ids.
442 * @return int
443 */
444 private function get_optin_id_by_conversions( $ids ) {
445 global $wpdb;
446
447 $ids_str = implode( ',', $ids );
448
449 $sql =
450 "SELECT
451 i.conv_id as `id`,
452 COUNT(*) AS `views`,
453 SUM((i.click = TRUE) + (i.order_id IS NOT NULL)) AS `conversions`
454 FROM {$this->db->interactions_table} i
455 WHERE i.conv_id IN ( {$ids_str} )
456 GROUP BY i.conv_id
457 ORDER BY `conversions` DESC, `views` ASC, `id` ASC
458 LIMIT 1";
459
460 $winner_id = $wpdb->get_var( $sql ); // phpcs:ignore
461
462 return intval( $winner_id );
463 }
464
465 /**
466 * Get the ID of the optin with highest leads collected for a given set of optins.
467 *
468 * @param array $ids optin ids.
469 * @return int
470 */
471 private function get_optin_id_by_leads( $ids ) {
472 global $wpdb;
473
474 $ids_str = implode( ',', $ids );
475
476 $sql =
477 "SELECT
478 l.conv_id as `id`,
479 COUNT(*) AS `leads`
480 FROM {$this->db->leads_table} l
481 WHERE l.conv_id IN ({$ids_str})
482 GROUP BY l.conv_id
483 ORDER BY `leads` DESC, `id` ASC
484 LIMIT 1";
485
486 $winner_id = $wpdb->get_var( $sql ); // phpcs:ignore
487
488 return intval( $winner_id );
489 }
490
491 /**
492 * Get the ID of the optin with highest conversion rate for a given set of optins.
493 *
494 * @param array $ids optin ids.
495 * @return int
496 */
497 private function get_optin_id_by_cr( $ids ) {
498 global $wpdb;
499
500 $ids_str = implode( ',', $ids );
501
502 $sql =
503 "SELECT
504 i.conv_id as `id`,
505 COUNT(*) AS `views`,
506 SUM((i.click = TRUE) + (i.order_id IS NOT NULL)) / COUNT(*) AS `cr`
507 FROM {$this->db->interactions_table} i
508 WHERE i.conv_id IN ({$ids_str})
509 GROUP BY i.conv_id";
510
511 $results = $wpdb->get_results( $sql ); // phpcs:ignore
512
513 if ( empty( $results ) ) {
514 return 0;
515 }
516
517 $scored = array();
518 $k = 100; // Smoothing factor.
519
520 foreach ( $results as $row ) {
521 $cr = floatval( $row->cr );
522 $views = intval( $row->views );
523
524 $score = ( $cr * $views ) / ( $views + $k );
525
526 $scored[] = array(
527 'id' => $row->id,
528 'views' => $views,
529 'cr' => $cr,
530 'score' => $score,
531 );
532 }
533
534 usort(
535 $scored,
536 function ( $a, $b ) {
537 $res = $b['score'] <=> $a['score'];
538
539 if ( 0 === $res ) {
540 return intval( $a['id'] ) <=> intval( $b['id'] );
541 }
542
543 return $res;
544 }
545 );
546
547 return $scored[0]['id'];
548 }
549
550 /**
551 * Process ab test
552 *
553 * @param object $posts posts.
554 * @return array
555 */
556 public static function check_ab_test( $posts ) {
557 $groups = array();
558 $ungrouped = array();
559
560 foreach ( $posts as $post ) {
561 if ( isset( $post->test_id ) && ! empty( $post->test_id ) ) {
562 $groups[ $post->test_id ][] = $post;
563 } else {
564 $ungrouped[] = $post;
565 }
566 }
567
568 $selected_posts = array();
569
570 // Picking a single post from each test group.
571 foreach ( $groups as $test_id => $g_posts ) {
572
573 // If the user has previously viewed a variant, we show it again without random selection.
574 // This is to make sure no bias is introduced in the winner selection.
575 $variant = self::get_previously_viewed_variant( $test_id, $g_posts );
576
577 // If no previous view data found, we select a random variant.
578 if ( empty( $variant ) ) {
579 $variant = self::get_weighted_rand_post( $g_posts, (int) $test_id );
580 }
581
582 if ( ! empty( $variant ) ) {
583 self::set_viewed_variant_id( $test_id, $variant->id );
584 $selected_posts[] = $variant;
585 }
586 }
587
588 return array_merge( $ungrouped, $selected_posts );
589 }
590
591 /**
592 * Get previously viewed variant by current user
593 *
594 * @param int $test_id ab test id.
595 * @param array $variants variants.
596 * @return object|null
597 */
598 private static function get_previously_viewed_variant( $test_id, $variants ) {
599 $user_hash = Utils::get_user_hash();
600
601 if ( empty( $user_hash ) ) {
602 return null;
603 }
604
605 $key = 'optn_abt_view_history_' . $test_id;
606 $view_history = get_option( $key, array() );
607
608 $var_id = isset( $view_history[ $user_hash ] ) ? $view_history[ $user_hash ] : null;
609
610 if ( empty( $var_id ) ) {
611 return null;
612 }
613
614 foreach ( $variants as $variant ) {
615 if ( (int) $variant->id === (int) $var_id ) {
616 return $variant;
617 }
618 }
619
620 return null;
621 }
622
623 /**
624 * Set viewed variant id for a user
625 *
626 * @param int $test_id ab test id.
627 * @param int $optin_id optin id.
628 * @return void
629 */
630 private static function set_viewed_variant_id( $test_id, $optin_id ) {
631 $key = 'optn_abt_view_history_' . $test_id;
632 $view_history = get_option( $key, array() );
633
634 $user_hash = Utils::get_user_hash();
635
636 if ( empty( $user_hash ) ) {
637 return;
638 }
639
640 $view_history[ $user_hash ] = (int) $optin_id;
641
642 update_option( $key, $view_history );
643 }
644
645 /**
646 * Create variant view history
647 *
648 * @param [type] $test_id ab test id.
649 * @return void
650 */
651 private static function create_variant_view_history( $test_id ) {
652 $key = 'optn_abt_view_history_' . $test_id;
653 $existing_history = get_option( $key, array() );
654 update_option( $key, $existing_history );
655 }
656
657 /**
658 * Delete variant view history
659 *
660 * @param string $test_id ab test id.
661 * @return void
662 */
663 private static function delete_variant_view_history( $test_id ) {
664 $key = 'optn_abt_view_history_' . $test_id;
665 delete_option( $key );
666 }
667
668 /**
669 * Get weighted random post
670 *
671 * @param array $posts posts.
672 * @param int|null $test_id ab test id.
673 * @return object|null
674 */
675 private static function get_weighted_rand_post( $posts, $test_id = null ) {
676 if ( empty( $posts ) ) {
677 return null;
678 }
679
680 $weights = array();
681 $total_weight = 0.0;
682
683 foreach ( $posts as $index => $post ) {
684 $weight = isset( $post->t_dist ) ? floatval( $post->t_dist ) : 0.0;
685 $weight = max( 0.0, $weight );
686 $weights[ $index ] = $weight;
687 $total_weight += $weight;
688 }
689
690 // If no valid weights are provided, use unbiased uniform selection.
691 if ( $total_weight <= 0 ) {
692 return self::get_uniform_rand_post( $posts, $test_id );
693 }
694
695 $fraction = self::get_seeded_fraction( $test_id );
696 if ( null === $fraction ) {
697 $fraction = wp_rand( 0, 1000000 ) / 1000000;
698 }
699
700 $threshold = $fraction * $total_weight;
701 $cumulative = 0.0;
702
703 foreach ( $posts as $index => $post ) {
704 $weight = isset( $weights[ $index ] ) ? $weights[ $index ] : 0.0;
705 if ( $weight <= 0 ) {
706 continue;
707 }
708
709 $cumulative += $weight;
710 if ( $threshold < $cumulative ) {
711 return $post;
712 }
713 }
714
715 return self::get_uniform_rand_post( $posts, $test_id );
716 }
717
718 /**
719 * Unbiased uniform fallback selector.
720 *
721 * @param array $posts posts.
722 * @param int|null $test_id ab test id.
723 * @return object|null
724 */
725 private static function get_uniform_rand_post( $posts, $test_id = null ) {
726 $count = count( $posts );
727
728 if ( $count < 1 ) {
729 return null;
730 }
731
732 if ( 1 === $count ) {
733 return $posts[0];
734 }
735
736 $index = self::get_seeded_index( $count, $test_id );
737
738 if ( null === $index ) {
739 $index = wp_rand( 0, $count - 1 );
740 }
741
742 return $posts[ $index ];
743 }
744
745 /**
746 * Returns a deterministic [0,1] fraction when a user hash exists.
747 *
748 * @param int|null $test_id ab test id.
749 * @return float|null
750 */
751 private static function get_seeded_fraction( $test_id = null ) {
752 $user_hash = Utils::get_user_hash();
753
754 if ( empty( $user_hash ) ) {
755 return null;
756 }
757
758 $seed = $user_hash . '|abt|' . strval( $test_id ) . '|weighted';
759 $hash = sprintf( '%u', crc32( $seed ) );
760 $max_uint = 4294967295;
761 $fraction = floatval( $hash ) / $max_uint;
762
763 return min( max( $fraction, 0.0 ), 1.0 );
764 }
765
766 /**
767 * Returns a deterministic index when a user hash exists.
768 *
769 * @param int $count posts count.
770 * @param int|null $test_id ab test id.
771 * @return int|null
772 */
773 private static function get_seeded_index( $count, $test_id = null ) {
774 $user_hash = Utils::get_user_hash();
775
776 if ( empty( $user_hash ) || $count < 1 ) {
777 return null;
778 }
779
780 $seed = $user_hash . '|abt|' . strval( $test_id ) . '|uniform';
781 $hash = sprintf( '%u', crc32( $seed ) );
782
783 return intval( $hash ) % $count;
784 }
785
786 /**
787 * Encodes a status value to its integer representation.
788 *
789 * @param string $status status.
790 * @return int
791 */
792 public static function encode_status( $status ) {
793 return isset( self::$status_map[ $status ] ) ? self::$status_map[ $status ] : 0;
794 }
795
796 /**
797 * Decodes a status integer representation to its string value.
798 *
799 * @param int $status status.
800 * @return string
801 */
802 public static function decode_status( $status ) {
803 if ( ! isset( self::$status_map_flip ) ) {
804 self::$status_map_flip = array_flip( self::$status_map );
805 }
806
807 return isset( self::$status_map_flip[ $status ] ) ? self::$status_map_flip[ $status ] : 'draft';
808 }
809
810 /**
811 * Encodes a type value to its integer representation.
812 *
813 * @param string $type type.
814 * @return int
815 */
816 public static function encode_type( $type ) {
817 return isset( self::$type_map[ $type ] ) ? self::$type_map[ $type ] : 0;
818 }
819
820 /**
821 * Decodes a type integer representation to its string value.
822 *
823 * @param int $type type.
824 * @return string
825 */
826 public static function decode_type( $type ) {
827 if ( ! isset( self::$type_map_flip ) ) {
828 self::$type_map_flip = array_flip( self::$type_map );
829 }
830
831 return isset( self::$type_map_flip[ $type ] ) ? self::$type_map_flip[ $type ] : 'manual';
832 }
833
834 /**
835 * Encodes a metric value to its integer representation.
836 *
837 * @param string $metric metric.
838 * @return int
839 */
840 public static function encode_metric( $metric ) {
841 return isset( self::$metrics_map[ $metric ] ) ? self::$metrics_map[ $metric ] : 0;
842 }
843
844 /**
845 * Decodes a metric integer representation to its string value.
846 *
847 * @param int $metric metric.
848 * @return string
849 */
850 public static function decode_metric( $metric ) {
851 if ( ! isset( self::$metrics_map_flip ) ) {
852 self::$metrics_map_flip = array_flip( self::$metrics_map );
853 }
854
855 return isset( self::$metrics_map_flip[ $metric ] ) ? self::$metrics_map_flip[ $metric ] : 'conv';
856 }
857 }
858