Commit inicial - WordPress Análisis de Precios Unitarios

- WordPress core y plugins
- Tema Twenty Twenty-Four configurado
- Plugin allow-unfiltered-html.php simplificado
- .gitignore configurado para excluir wp-config.php y uploads

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
root
2025-11-03 21:04:30 -06:00
commit a22573bf0b
24068 changed files with 4993111 additions and 0 deletions

View File

@@ -0,0 +1,409 @@
<?php
/**
* Thrive Themes - https://thrivethemes.com
*
* @package thrive-ab-page-testing
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Silence is golden
}
class Thrive_AB_Test_Item extends Thrive_AB_Model {
/**
* @var Thrive_AB_Test_Item
*/
protected $control;
/**
* @return array
*/
public function get_data() {
return array(
'id' => $this->id,
'variation_id' => $this->variation_id,
'test_id' => $this->test_id,
'thankyou_id' => $this->thankyou_id,
'page_id' => $this->page_id,
'title' => $this->title,
'is_control' => (bool) $this->is_control,
'active' => $this->active,
'is_winner' => $this->is_winner,
'impressions' => $this->impressions,
'unique_impressions' => $this->unique_impressions,
'stopped_date' => date( 'j F Y', strtotime( $this->stopped_date ) ),
'preview_link' => $this->get_preview_link(),
'editor_link' => $this->get_editor_link(),
'traffic' => $this->get_traffic(),
'conversions' => $this->conversions,
'revenue' => $this->revenue,
'revenue_visitor' => $this->revenue_per_visitor(),
'conversion_rate' => $this->conversion_rate(),
'improvement' => $this->get_improvement(),
'chance_to_beat_orig' => $this->get_chance_to_beat_original(),
);
}
public function get_preview_link() {
$url = '';
/**@var $this ->>variation Thrive_AB_Variation */
if ( $this->variation instanceof Thrive_AB_Variation ) {
$url = $this->variation->get_preview_url();
}
return $url;
}
public function get_editor_link() {
$url = '';
/**@var $this ->>variation Thrive_AB_Variation */
if ( $this->variation instanceof Thrive_AB_Variation ) {
if ( current_user_can( 'edit_post', $this->variation->get_data()['ID'] ) ) {
$url = $this->variation->get_editor_url();
}
}
return $url;
}
public function get_traffic() {
$traffic = '';
/**@var $this ->>variation Thrive_AB_Variation */
if ( $this->variation instanceof Thrive_AB_Variation ) {
$traffic = (int) $this->variation->get_meta()->get( 'traffic' );
}
return $traffic;
}
/**
* @return mixed|null
*/
public function get_impressions() {
return $this->unique_impressions;
}
/**
* @return mixed|null
*/
public function get_conversions() {
return $this->conversions;
}
/**
* @return float
*/
public function get_chance_to_beat_original() {
$chance = $this->chance( $this->get_control()->conversion_rate(), $this->get_control()->unique_impressions );
if ( $chance === false ) {
return 0.0;
}
return $chance;
}
/**
* Returns the improvement for current test item
*
* @return float
*/
public function get_improvement() {
$control_conversion_rate = $this->get_control()->conversion_rate();
if ( $control_conversion_rate == 0 ) {
return 0;
}
return round( ( ( $this->conversion_rate() - $control_conversion_rate ) * 100 ) / $control_conversion_rate, 2 );
}
/**
* Calculates the revenue per visitor
*
* @return float
*/
public function revenue_per_visitor() {
$this->unique_impressions = (int) $this->unique_impressions;
$value = 0.0;
if ( $this->unique_impressions !== 0 ) {
$value = round( $this->revenue / $this->unique_impressions, 2 );
}
return $value;
}
/**
* Calculate conversion rate for current test item
*
* @return float
*/
public function conversion_rate() {
$conversions = (int) $this->conversions;
$impressions = (int) $this->unique_impressions;
$conversion_rate = 0.0;
if ( $conversions !== 0 && $impressions !== 0 ) {
$conversion_rate = round( 100 * ( $conversions / $impressions ), 2 );
}
return $conversion_rate;
}
/**
* Calculate the chance of current variation to beat the original during a test
*
* @param float $control_conversion_rate
* @param float $control_unique_impressions
*
* @return string
*/
public function chance( $control_conversion_rate, $control_unique_impressions ) {
if ( $this->unique_impressions == 0 || $control_unique_impressions == 0 ) {
return false;
}
$variation_conversion_rate = $this->conversion_rate() / 100;
$control_conversion_rate = (float) $control_conversion_rate / 100;
//standard deviation = sqrt((conversionRate*(1-conversionRate)/uniqueImpressions)
$variation_standard_deviation = sqrt( ( $variation_conversion_rate * ( 1 - $variation_conversion_rate ) / $this->unique_impressions ) );
$control_standard_deviation = sqrt( ( $control_conversion_rate * ( 1 - $control_conversion_rate ) / $control_unique_impressions ) );
if ( ( $variation_standard_deviation == 0 && $control_standard_deviation == 0 ) || ( is_nan( $variation_standard_deviation ) || is_nan( $control_standard_deviation ) ) ) {
return false;
}
//z-score = (control_conversion_rate - variation_conversion_rate) / sqrt((controlStandardDeviation^2)+(variationStandardDeviation^2))
$z_score = ( $control_conversion_rate - $variation_conversion_rate ) / sqrt( pow( $control_standard_deviation, 2 ) + pow( $variation_standard_deviation, 2 ) );
if ( is_nan( $z_score ) ) {
return false;
}
//Confidence_level (which is synonymous with “chance to beat original”) = normdist(z-score)
$confidence_level = $this->normal_distribution( $z_score );
if ( $confidence_level === false ) {
return false;
}
return number_format( round( ( 1 - $confidence_level ) * 100, 2 ), 2 );
}
/**
* Function that will generate a cumulative normal distribution and return the confidence level as a number between 0 and 1
*
* @param $x
*
* @return float
*/
protected function normal_distribution( $x ) {
$b1 = 0.319381530;
$b2 = - 0.356563782;
$b3 = 1.781477937;
$b4 = - 1.821255978;
$b5 = 1.330274429;
$p = 0.2316419;
$c = 0.39894228;
if ( $x >= 0.0 ) {
if ( ( 1.0 + $p * $x ) == 0 ) {
return false;
}
$t = 1.0 / ( 1.0 + $p * $x );
return ( 1.0 - $c * exp( - $x * $x / 2.0 ) * $t * ( $t * ( $t * ( $t * ( $t * $b5 + $b4 ) + $b3 ) + $b2 ) + $b1 ) );
}
if ( ( 1.0 - $p * $x ) == 0 ) {
return false;
}
$t = 1.0 / ( 1.0 - $p * $x );
return ( $c * exp( - $x * $x / 2.0 ) * $t * ( $t * ( $t * ( $t * ( $t * $b5 + $b4 ) + $b3 ) + $b2 ) + $b1 ) );
}
/**
* @inheritdoc
*/
protected function _table_name() {
return thrive_ab()->table_name( 'test_items' );
}
/**
* @inheritdoc
*/
protected function _get_default_data() {
$defaults = array(
'is_control' => 0,
'is_winner' => 0,
'impressions' => 0,
'unique_impressions' => 0,
'conversions' => 0,
'revenue' => 0.0,
'active' => true,
'stopped_date' => null,
);
return $defaults;
}
/**
* @inheritdoc
*/
protected function is_valid() {
$is_valid = true;
if ( ! ( $this->page_id ) ) {
$is_valid = false;
} elseif ( ! ( $this->variation_id ) ) {
$is_valid = false;
} elseif ( ! ( $this->test_id ) ) {
$is_valid = false;
} elseif ( ! ( $this->title ) ) {
$is_valid = false;
}
return $is_valid;
}
public function get_total_conversions( $test_id ) {
$sql = 'SELECT SUM(conversions) as total_conversions FROM ' . $this->_table_name() . ' WHERE test_id = %d LIMIT 1';
$params = array( $test_id );
return $this->wpdb->get_row( $this->wpdb->prepare( $sql, $params ) );
}
/**
* Init by filters
*
* @param array $filters
*
* @throws Exception
*/
public function init_by_filters( $filters = array() ) {
if ( empty( $filters ) ) {
throw new Exception( __( 'Invalid filters', 'thrive-ab-page-testing' ) );
}
$sql = 'SELECT * FROM ' . $this->_table_name() . ' WHERE 1 ';
$params = array();
if ( ! empty( $filters['id'] ) ) {
$sql .= 'AND `id` = %d ';
$params [] = $filters['id'];
}
if ( ! empty( $filters['page_id'] ) ) {
$sql .= 'AND `page_id` = %d ';
$params [] = $filters['page_id'];
}
if ( ! empty( $filters['variation_id'] ) ) {
$sql .= 'AND `variation_id` = %d ';
$params [] = $filters['variation_id'];
}
if ( ! empty( $filters['test_id'] ) ) {
$sql .= 'AND `test_id` = %d ';
$params [] = $filters['test_id'];
}
if ( ! empty( $filters['active'] ) ) {
$sql .= 'AND `active` = %d ';
$params [] = $filters['active'];
}
$sql_prepared = $this->wpdb->prepare( $sql, $params );
$result = $this->wpdb->get_row( $sql_prepared, ARRAY_A );
if ( ! empty( $result ) ) {
$this->_data = $result;
}
}
public function get_control() {
if ( $this->control instanceof Thrive_AB_Test_Item ) {
return $this->control;
}
$sql = 'SELECT * FROM ' . $this->_table_name() . ' WHERE is_control = 1 AND test_id = %d LIMIT 1';
$params = array( $this->test_id );
$sql_prepared = $this->wpdb->prepare( $sql, $params );
$result = $this->wpdb->get_row( $sql_prepared, ARRAY_A );
if ( ! empty( $result ) ) {
$this->control = new Thrive_AB_Test_Item( $result );
} else {
$this->control = new Thrive_AB_Test_Item();
}
return $this->control;
}
/**
* Stops a test item
*
* @throws Exception
*/
public function stop() {
$this->active = 0;
$this->stopped_date = date( 'Y-m-d H:i:s' );
return $this;
}
/**
* @inheritdoc
*/
protected function _prepare_data() {
$data = $this->_data;
$save_data = array(
'id' => null,
'page_id' => null,
'variation_id' => null,
'test_id' => null,
'thankyou_id' => null,
'goal_pages' => null,
'title' => null,
'is_control' => null,
'is_winner' => null,
'impressions' => null,
'unique_impressions' => null,
'conversions' => null,
'revenue' => null,
'active' => null,
'stopped_date' => null,
);
$save_data = array_intersect_key( $data, $save_data );
if ( empty( $save_data['id'] ) ) {
unset( $save_data['id'] );
}
$save_data['is_control'] = (int) $save_data['is_control'];
return $save_data;
}
}

View File

@@ -0,0 +1,278 @@
<?php
/**
* Thrive Themes - https://thrivethemes.com
*
* @package thrive-ab-page-testing
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Silence is golden
}
/**
* Class Thrive_AB_Test_Manager
*
* This class can be instantiate but it also has static methods to easily work with test models
*/
class Thrive_AB_Test_Manager {
public static $types
= array(
'monetary',
'visits',
'optins',
);
/**
* @var wpdb
*/
protected $_wpdb;
public function __construct() {
global $wpdb;
$this->_wpdb = $wpdb;
}
/**
* init some hooks
*/
public static function init() {
/**
* before TAr template_redirect which is at 0 priority
*/
// add_action( 'template_redirect', array( __CLASS__, 'template_redirect' ), - 1 );
}
/**
* redirects the user to post parent if he wants to edit the page with TAr
* and a test is running
*/
public static function template_redirect() {
if ( ! is_editor_page() ) {
return;
}
global $post;
$post_id = $post->ID;
/**
* if the posts is a variation
* check its parent for test
*/
if ( $post->post_type === Thrive_AB_Post_Types::VARIATION ) {
$post_id = $post->post_parent;
}
$instance = new Thrive_AB_Test_Manager();
$tests = $instance->get_running_test( $post_id );
if ( ! empty( $tests ) ) {
wp_redirect( get_permalink( $post_id ) );
die;
}
}
/**
* Store the model into db and return it
* If model has items prop set it tries to save those into db and push them into local _data
*
* @param $model
*
* @return Thrive_AB_Test
* @throws Exception
*/
public static function save_test( $model ) {
$test = new Thrive_AB_Test( $model );
$test->save();
try {
if ( ! empty( $model['items'] ) ) {
$test->items = array();
foreach ( $model['items'] as $item ) {
$item['test_id'] = $test->id;
$item['page_id'] = $test->page_id;
$test->save_item( $item );
}
}
} catch ( Exception $e ) {
$test->delete();
throw new Exception( $e->getMessage() );
}
if ( ! empty( $model['page_id'] ) ) {
$page = new Thrive_AB_Page( (int) $model['page_id'] );
if ( 'draft' === $page->post_status ) {
$page->get_post()->post_status = 'publish';
wp_update_post( $page->get_post() );
}
}
return $test;
}
/**
* Delete a test
*
* @param array $model
*/
public static function delete_test( $model = array() ) {
if ( empty( $model ) ) {
return;
}
$test = new Thrive_AB_Test( $model );
$test->delete();
}
/**
* Based on $filters a list of test models should be read from DB and returned
*
* @param array $filters
* @param string $type
*
* @return array
*/
public function get_tests( $filters = array(), $type = 'array' ) {
$tests = array();
$where = ' WHERE 1=1 ';
$filters = array_merge( array(), $filters );
$params = array();
if ( ! empty( $filters['page_id'] ) ) {
$where .= ' AND `page_id` = %s';
$params[] = $filters['page_id'];
}
if ( ! empty( $filters['status'] ) ) {
$where .= ' AND `status` = %s';
$params[] = $filters['status'];
}
if ( ! empty( $filters['id'] ) ) {
$where .= ' AND `id` = %s';
$params[] = $filters['id'];
}
$sql = 'SELECT * FROM ' . thrive_ab()->table_name( 'tests' ) . $where;
if ( ! empty( $params ) ) {
$sql = $this->_wpdb->prepare( $sql, $params );
}
$results = $this->_wpdb->get_results( $sql, ARRAY_A );
if ( ! empty( $results ) ) {
foreach ( $results as $test ) {
$tmp = new Thrive_AB_Test( $test );
$tests[] = $type === 'array' ? $tmp->get_data() : $tmp;
}
}
return $tests;
}
/**
* Returns an array of test items by filters
*
* @param array $filters
* @param string $output
*
* @return array|null|object
*/
public function get_items_by_filters( $filters = array(), $output = ARRAY_A ) {
$where = ' WHERE 1=1 ';
$sql = 'SELECT * FROM ' . thrive_ab()->table_name( 'test_items' ) . $where;
$params = array();
if ( ! empty( $filters['test_id'] ) ) {
$sql .= 'AND `test_id` = %d ';
$params [] = $filters['test_id'];
}
if ( ! empty( $filters['active'] ) ) {
$sql .= 'AND `active` = %d ';
$params [] = $filters['active'];
}
$sql_prepared = $this->_wpdb->prepare( $sql, $params );
$results = $this->_wpdb->get_results( $sql_prepared, $output );
return $results;
}
/**
* Get running test based on page_id
*
* @param int $page_id
*
* @return Thrive_AB_Test|null
*/
public function get_running_test( $page_id ) {
$filters = array(
'page_id' => $page_id,
'status' => 'running',
);
$tests = $this->get_tests( $filters, 'object' );
return empty( $tests ) ? null : reset( $tests );
}
protected static function get_goals( $goal = '' ) {
$goals = array(
'monetary' => array(
'icon' => 'monetary-2',
'label' => __( 'Goal one', 'thrive-ab-page-testing' ),
'name' => __( 'Revenue', 'thrive-ab-page-testing' ),
),
'visits' => array(
'icon' => 'visit_gp',
'label' => __( 'Goal two', 'thrive-ab-page-testing' ),
'name' => __( 'Visit goal page', 'thrive-ab-page-testing' ),
),
'optins' => array(
'icon' => 'subs',
'label' => __( 'Goal three', 'thrive-ab-page-testing' ),
'name' => __( 'Subscriptions', 'thrive-ab-page-testing' ),
),
);
return in_array( $goal, self::$types ) ? $goals[ $goal ] : $goals;
}
public static function display_goal_option( $goal ) {
$data = self::get_goals( $goal );
$icon = tcb_icon( $data['icon'], true, 'sidebar' );
$label = $data['label'];
$name = $data['name'];
include dirname( __FILE__ ) . '/../views/backbone/goals/goal.php';
}
/**
* @param $test_id int
*
* @return false|string
*/
public static function get_test_url( $test_id ) {
$url = add_query_arg( array(
'page' => 'tab_admin_view_test',
'test_id' => $test_id,
), admin_url( 'admin.php' ) ) . '#test';
return $url;
}
}
Thrive_AB_Test_Manager::init();

View File

@@ -0,0 +1,327 @@
<?php
/**
* Thrive Themes - https://thrivethemes.com
*
* @package thrive-ab-page-testing
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Silence is golden
}
/**
* Class Thrive_AB_Test
*
* - id int primary key
* - type string: monetary, visits, optins
* - page_id int
* - thank_you_id int
* - date_started datetime
* - date_completed datetime
* - title string
* - notes string
* - auto_win_enabled bool
* - auto_win_min_conversions int
* - auto_win_min_duration int days
* - auto_win_chance_original float
* - status string: running, completed, archived
*
* Dynamic properties
* - items array of test_items instances
*
* array associative which has indexes columns from DB
*/
class Thrive_AB_Test extends Thrive_AB_Model {
protected function _get_default_data() {
$_items_count = ! empty( $this->items ) && is_array( $this->items ) ? count( $this->items ) : 0;
$defaults = array(
'date_added' => date( 'Y-m-d H:i:s' ),
'date_started' => date( 'Y-m-d H:i:s' ),
'auto_win_enabled' => true,
'auto_win_min_conversions' => $this->_get_default_win_conversions( $_items_count ),
'auto_win_min_duration' => 14,
'auto_win_chance_original' => 95,
'status' => 'running',
'items' => array(),
);
return $defaults;
}
public function get_data() {
$data = array(
'id' => $this->id,
'goal_pages' => $this->goal_pages,
'page_id' => $this->page_id,
'title' => $this->title,
'notes' => $this->notes,
'type' => $this->type,
'auto_win_enabled' => $this->auto_win_enabled,
'auto_win_min_conversions' => $this->auto_win_min_conversions,
'auto_win_min_duration' => $this->auto_win_min_duration,
'auto_win_chance_original' => $this->auto_win_chance_original,
'date_started' => $this->date_started,
'date_completed' => $this->date_completed,
'status' => $this->status,
'items' => $this->items,
);
$items = array();
/** @var Thrive_AB_Test_Item $item */
foreach ( $this->items as $item ) {
$items[] = $item->get_data();
}
$data['items'] = $items;
if ( is_array( $data['goal_pages'] ) ) {
foreach ( $data['goal_pages'] as $key => &$page ) {
$page['permalink'] = get_permalink( $page['post_id'] );
$page['tcb_edit_url'] = tcb_get_editor_url( $page['post_id'] );
}
}
return $data;
}
/**
* @inheritdoc
*
* @return bool
*/
protected function is_valid() {
$is_valid = true;
if ( ! ( $this->page_id ) ) {
$is_valid = false;
} elseif ( ! ( $this->title ) ) {
$is_valid = false;
} elseif ( ! ( $this->type ) ) {
$is_valid = false;
}
return $is_valid;
}
/**
* @inheritdoc
*
* @return array
*/
protected function _prepare_data() {
$data = $this->_data;
if ( isset( $data['items'] ) ) {
unset( $data['items'] );
}
if ( ! empty( $data['goal_pages'] ) ) {
$data['goal_pages'] = maybe_serialize( $data['goal_pages'] );
}
//use goal_pages column from test tables to store monetary services
if ( ! empty( $data['service'] ) && 'visit_page' !== $data['service'] && 'monetary' === $data['type'] ) {
$data['goal_pages'] = $data['service'];
unset( $data['service'] );
}
if ( isset( $data['service'] ) ) {
unset( $data['service'] );
}
if ( empty( $data['id'] ) ) {
unset( $data['id'] );
}
return $data;
}
/**
* Calc default winning conversions based on how many items current test has
*
* @param $test_items_count int
*
* @return int
*/
protected function _get_default_win_conversions( $test_items_count ) {
return abs( ( $test_items_count - 1 ) * 100 );
}
/**
* @inheritdoc
*/
protected function _table_name() {
return thrive_ab()->table_name( 'tests' );
}
/**
* Save the model into db and push it into _data[items]
*
* @param $model
*
* @return Thrive_AB_Test_Item
*/
public function save_item( $model ) {
$item = new Thrive_AB_Test_Item( $model );
$item->save();
if ( ! $this->items || ! is_array( $this->items ) ) {
$this->items = array();
}
$this->_data['items'][] = $item;
return $item;
}
public function delete() {
delete_transient( Thrive_AB_Report_Manager::$_transient_stats_name );
parent::delete();
$this->delete_test_items();
/**
* Delete also log entries
*/
Thrive_AB_Event_Manager::bulk_delete_log( array( 'test_id' => $this->id ) );
}
public function get_items() {
if ( empty( $this->items ) ) {
global $wpdb;
$sql = $this->wpdb->prepare(
'SELECT *
FROM ' . thrive_ab()->table_name( 'test_items' ) . ' AS tt
INNER JOIN ' . $wpdb->prefix . 'posts AS p ON p.ID = tt.variation_id
WHERE test_id = %s ORDER BY is_control DESC',
array(
$this->id,
)
);
$items = $this->wpdb->get_results( $sql, ARRAY_A );
if ( count( $items ) ) {
$control = new Thrive_AB_Test_Item( $items[0] );
foreach ( $items as $key => $item ) {
$item['control'] = $control;
$item['variation'] = new Thrive_AB_Page_Variation( (int) $item['variation_id'] );
$items[ $key ] = new Thrive_AB_Test_Item( $item );
}
$this->items = $items;
}
}
return $this->items;
}
public function start() {
$this->status = 'running';
$this->date_started = date( 'Y-m-d H:i:s' );
if ( $this->page_id && $this->id ) {
try {
$page = new Thrive_AB_Page( (int) $this->page_id );
$page->get_meta()->update( 'running_test_id', $this->id );
} catch ( Exception $e ) {
}
}
if ( function_exists( 'tve_flush_cache' ) ) {
tve_flush_cache( $this->page_id );
}
/**
* purge cache for goal pages
*/
$goal_pages = is_array( $this->goal_pages ) ? $this->goal_pages : maybe_unserialize( $this->goal_pages );
$goal_pages = is_array( $goal_pages ) ? array_keys( $goal_pages ) : array();
foreach ( $goal_pages as $page_id ) {
if ( function_exists( 'tve_flush_cache' ) ) {
tve_flush_cache( $page_id );
}
}
return $this;
}
public function stop() {
$this->status = 'completed';
$this->date_completed = date( 'Y-m-d H:i:s' );
if ( $this->page_id && $this->id ) {
$page = new Thrive_AB_Page( (int) $this->page_id );
$page->get_meta()->update( 'running_test_id', false );
}
return $this;
}
protected function delete_test_items() {
try {
//try to delete test post variations
$items = $this->get_items();
/** @var Thrive_AB_Test_Item $item */
foreach ( $items as $key => $item ) {
if ( $item->variation && $item->variation instanceof Thrive_AB_Page_Variation ) {
$item->variation->delete();
}
}
} catch ( Exception $e ) {
}
$this->wpdb->delete( thrive_ab()->table_name( 'test_items' ), array( 'test_id' => $this->id ) );
}
/**
* Get goal pages
*
* @return array|mixed
*/
public function goal_pages() {
return ! empty( $this->_data['goal_pages'] ) ? maybe_unserialize( $this->_data['goal_pages'] ) : array();
}
public function set_goal_pages( $value ) {
$this->_data['goal_pages'] = maybe_unserialize( $value );
return $this->_data['goal_pages'];
}
public function has_goal_page( $post_id ) {
$goal_pages = $this->goal_pages();
return is_array( $goal_pages ) && in_array( $post_id, array_keys( $goal_pages ) );
}
public function get_goal_page_details( $id ) {
$goal_pages = $this->goal_pages();
$details = null;
if ( is_array( $goal_pages ) && ! empty( $goal_pages[ $id ] ) ) {
$details = $goal_pages[ $id ];
}
return $details;
}
}