| Server IP : 65.109.86.83 / Your IP : 216.73.216.140 Web Server : nginx/1.14.1 System : Linux libra 4.18.0-553.51.1.el8_10.x86_64 #1 SMP Wed Apr 30 20:24:04 UTC 2025 x86_64 User : root ( 0) PHP Version : 8.3.31 Disable Function : exec,passthru,shell_exec,system,proc_open,popen,parse_ini_file,show_source MySQL : OFF | cURL : ON | WGET : OFF | Perl : OFF | Python : OFF | Sudo : OFF | Pkexec : OFF Directory : /var/www/html/wp-content/plugins/thrive-quiz-builder/includes/ |
Upload File : |
<?php
/**
* Handles database operations
* Created by PhpStorm.
* User: Ovidiu
* Date: 9/22/2016
* Time: 5:16 PM
*/
global $tqbdb;
use TCB\inc\helpers\FormSettings;
/**
* Encapsulates the global $wpdb object
*
* Class Tho_Db
*/
class TQB_Database {
/**
* @var $wpdb wpdb
*/
protected $wpdb = null;
/**
* Request-level cache for quiz user lookups
* @var array
*/
private static $user_cache = array();
/**
* class constructor
*/
public function __construct() {
global $wpdb;
$this->wpdb = $wpdb;
}
/**
* forward the call to the $wpdb object
*
* @param $method_name
* @param $args
*
* @return mixed
*/
public function __call( $method_name, $args ) {
return call_user_func_array( array( $this->wpdb, $method_name ), $args );
}
/**
* unserialize fields from an array
*
* @param array $array where to search the fields
* @param array $fields fields to be unserialized
*
* @return array the modified array containing the unserialized fields
*/
protected function _unserialize_fields( $array, $fields = array() ) {
foreach ( $fields as $field ) {
if ( ! isset( $array[ $field ] ) ) {
continue;
}
/* the serialized fields should be trigger_config and tcb_fields */
$array[ $field ] = empty( $array[ $field ] ) ? array() : unserialize( $array[ $field ] );
$array[ $field ] = wp_unslash( $array[ $field ] );
/* extra checks to ensure we'll have consistency */
if ( ! is_array( $array[ $field ] ) ) {
$array[ $field ] = array();
}
}
return $array;
}
/**
*
* replace table names in form of {table_name} with the prefixed version
*
* @param $sql
* @param $params
*
* @return false|null|string
*/
public function prepare( $sql, $params ) {
$prefix = tqb_table_name( '' );
$sql = preg_replace( '/\{(.+?)\}/', '`' . $prefix . '$1' . '`', $sql );
if ( strpos( $sql, '%' ) === false ) {
return $sql;
}
return $this->wpdb->prepare( $sql, $params );
}
/**
* save a variation into the database
*
* @param array $model
*
* @return int
*/
public function save_variation( $model = array() ) {
$_columns = array(
'id',
'quiz_id',
'date_added',
'date_modified',
'page_id',
'parent_id',
'is_control',
'post_status',
'post_title',
'cache_impressions',
'cache_optins',
'cache_optins_conversions',
'cache_social_shares',
'cache_social_shares_conversions',
'tcb_fields',
'content',
);
$data = array();
foreach ( $_columns as $key ) {
if ( isset( $model[ $key ] ) ) {
$data[ $key ] = $model[ $key ];
}
}
if ( ! empty( $data['tcb_fields'] ) ) {
$data['tcb_fields'] = serialize( $data['tcb_fields'] );
} else {
unset( $data['tcb_fields'] );
}
if ( ! empty( $data['content'] ) ) {
$data['content'] = wp_unslash( $data['content'] );
}
if ( ! empty( $data['id'] ) ) {
$data['date_modified'] = date( 'Y-m-d H:i:s' );
$update_rows = $this->wpdb->update( tqb_table_name( 'variations' ), $data, array( 'id' => $data['id'] ) );
if ( $update_rows !== false ) {
return $data['id'];
}
return $update_rows;
}
$this->wpdb->insert( tqb_table_name( 'variations' ), $data );
return $this->wpdb->insert_id;
}
/**
* Get the running test items
*
* @param array $filters
* @param $return_type
*
* @return array|null|object
*/
public function get_test_items( $filters = array(), $return_type = ARRAY_A ) {
$sql = 'SELECT * FROM ' . tqb_table_name( 'tests_items' ) . ' WHERE 1 ';
$params = array();
if ( ! empty( $filters['id'] ) ) {
$sql .= ' AND id = %s';
$params [] = $filters['id'];
}
if ( ! empty( $filters['test_id'] ) ) {
$sql .= ' AND test_id = %d';
$params [] = $filters['test_id'];
}
if ( ! empty( $filters['is_control'] ) ) {
$sql .= ' AND is_control = %s';
$params [] = $filters['is_control'];
}
if ( ! empty( $filters['is_winner'] ) ) {
$sql .= ' AND is_winner = %s';
$params [] = $filters['is_winner'];
}
if ( isset( $filters['active'] ) ) {
$sql .= ' AND active = %d';
$params [] = $filters['active'];
}
if ( ! empty( $filters['id'] ) ) {
return $this->wpdb->get_row( $this->prepare( $sql, $params ), $return_type );
}
$sql .= ' ORDER BY id ASC';
$models = $this->wpdb->get_results( $this->prepare( $sql, $params ), $return_type );
return $models;
}
/**
* Gets the test for checking the auto win settings
*
* @param array $filters
* @param bool $single
* @param string $return_type
*
* @return array|null|object|void
*/
public function get_tests( $filters = array(), $single = false, $return_type = ARRAY_A ) {
$params = array();
$query = 'SELECT tests.*, SUM(items.impressions) as impressions, SUM(items.optins_conversions) as optins_conversions, SUM(items.social_shares_conversions) as social_shares_conversions
FROM ' . tqb_table_name( 'tests' ) . ' AS tests
INNER JOIN ' . tqb_table_name( 'tests_items' ) . ' AS items ON tests.id = items.test_id
WHERE 1 ';
if ( ! empty( $filters['test_id'] ) ) {
$query .= " AND tests.id = '%d'";
$params[] = $filters['test_id'];
}
if ( isset( $filters['status'] ) ) {
$query .= " AND tests.status = '%d'";
$params[] = $filters['status'];
}
/*Fetch only the active items*/
$query .= " AND items.active = '%d'";
$params[] = 1;
$query .= ' GROUP BY tests.id ORDER BY tests.id DESC';
if ( $single ) {
return $this->wpdb->get_row( $this->prepare( $query, $params ), $return_type );
} else {
return $this->wpdb->get_results( $this->prepare( $query, $params ), $return_type );
}
}
/**
*
* Gets the quiz page variations
*
* @param array $filters
* @param string $return_type
*
* @return array|null|object|void
*/
public function get_page_variations( $filters = array(), $return_type = ARRAY_A ) {
$sql = 'SELECT * FROM ' . tqb_table_name( 'variations' ) . ' WHERE 1 ';
$params = array();
if ( ! empty( $filters['id'] ) ) {
$sql .= ' AND id = %s';
$params [] = $filters['id'];
}
if ( ! empty( $filters['post_id'] ) ) {
if ( is_array( $filters['post_id'] ) ) {
$sql .= ' AND page_id IN (' . implode( ',', $filters['post_id'] ) . ')';
} else {
$sql .= ' AND page_id = %d';
$params [] = $filters['post_id'];
}
}
if ( ! empty( $filters['post_status'] ) ) {
$sql .= ' AND post_status = %s';
$params [] = $filters['post_status'];
}
/*check for parent id*/
$sql .= ' AND parent_id = %s';
if ( empty( $filters['parent_id'] ) ) {
/*fetch only parent variations*/
$params [] = 0;
} else {
/*For child variations*/
$params [] = $filters['parent_id'];
}
/*can be 0 or 1*/
if ( isset( $filters['is_control'] ) && is_numeric( $filters['is_control'] ) ) {
$sql .= ' AND is_control = %d';
$params [] = $filters['is_control'];
}
$sql .= ' ORDER BY id ASC';
if ( ( ! empty( $filters['id'] ) ) || ( ! empty( $filters['is_control'] ) ) ) {
return $this->wpdb->get_row( $this->prepare( $sql, $params ), $return_type );
}
$models = $this->wpdb->get_results( $this->prepare( $sql, $params ), $return_type );
foreach ( $models as $key => $model ) {
if ( is_object( $model ) ) {
$models[ $key ]->cache_optin_conversion_rate = tqb_conversion_rate( $model->cache_impressions, $model->cache_optins_conversions );
$models[ $key ]->cache_social_share_conversion_rate = tqb_conversion_rate( $model->cache_social_shares, $model->cache_social_shares_conversions );
$models[ $key ]->tcb_fields = unserialize( $model->tcb_fields );
} else {
$models[ $key ]['cache_optin_conversion_rate'] = tqb_conversion_rate( $model['cache_impressions'], $model['cache_optins_conversions'] );
$models[ $key ]['cache_social_share_conversion_rate'] = tqb_conversion_rate( $model['cache_social_shares'], $model['cache_social_shares_conversions'] );
$models[ $key ]['tcb_fields'] = unserialize( $model['tcb_fields'] );
}
}
return $models;
}
/**
* Counts the quiz page variations
*
* @param array $filters
*
* @return null|string
*/
public function count_page_variations( $filters = array() ) {
$sql = 'SELECT COUNT(id) FROM ' . tqb_table_name( 'variations' ) . ' WHERE 1 ';
$params = array();
if ( ! empty( $filters['post_status'] ) ) {
$sql .= ' AND post_status = %s';
$params [] = $filters['post_status'];
}
if ( ! empty( $filters['post_id'] ) ) {
$sql .= ' AND page_id = %d';
$params [] = $filters['post_id'];
}
if ( ! empty( $filters['quiz_id'] ) ) {
$sql .= ' AND quiz_id = %d';
$params [] = $filters['quiz_id'];
}
/*can be 0 or 1*/
if ( isset( $filters['is_control'] ) && is_numeric( $filters['is_control'] ) ) {
$sql .= ' AND is_control = %d';
$params [] = $filters['is_control'];
}
return $this->wpdb->get_var( $this->prepare( $sql, $params ) );
}
/**
* Get test according to filters
*
* @param array $filters
* @param bool $single
* @param string $return_type
*
* @return array|null|object|void
*/
public function get_test( $filters = array(), $single = false, $return_type = ARRAY_A ) {
$params = array();
$where = ' 1=1 ';
if ( ! empty( $filters['id'] ) ) {
$params ['id'] = $filters['id'];
$where .= 'AND `id`=%d ';
}
if ( ! empty( $filters['page_id'] ) ) {
$params ['page_id'] = $filters['page_id'];
$where .= 'AND `page_id`=%d ';
}
if ( isset( $filters['status'] ) ) {
$params ['status'] = $filters['status'];
$where .= 'AND `status`=%d ';
}
$sql = 'SELECT * FROM ' . tqb_table_name( 'tests' ) . ' WHERE ' . $where;
if ( $single ) {
return $this->wpdb->get_row( $this->prepare( $sql, $params ), $return_type );
}
return $this->wpdb->get_results( $this->prepare( $sql, $params ), $return_type );
}
/**
* Deletes quiz variations
*
* @param array $filters
*
* @return false|int
*/
public function delete_variations( $filters = array() ) {
$params = array();
if ( ! empty( $filters['id'] ) ) {
$params ['id'] = $filters['id'];
}
if ( ! empty( $filters['quiz_id'] ) ) {
$params ['quiz_id'] = $filters['quiz_id'];
}
if ( ! empty( $filters['page_id'] ) ) {
$params ['page_id'] = $filters['page_id'];
}
/*can be 0 or 1*/
if ( isset( $filters['parent_id'] ) && is_numeric( $filters['parent_id'] ) ) {
$params ['parent_id'] = $filters['parent_id'];
}
if ( empty( $params ) ) {
/* we need at least one parameter so we won't empty the table by mistake */
return 0;
} else {
$this->delete_logs( $params );
return $this->wpdb->delete( tqb_table_name( 'variations' ), $params );
}
}
/**
* Deletes logs
*
* @param array $filters
*
* @return false|int
*/
public function delete_logs( $filters = array() ) {
$params = array();
if ( ! empty( $filters['id'] ) ) {
$params ['variation_id'] = $filters['id'];
}
if ( ! empty( $filters['page_id'] ) ) {
$params ['page_id'] = $filters['page_id'];
}
if ( empty( $params ) ) {
return false;
}
return $this->wpdb->delete( tqb_table_name( 'event_log' ), $params );
}
public function delete_multiple_logs( $filters ) {
$sql = 'DELETE FROM {event_log} WHERE 1=1';
$where = '';
$params = array();
if ( ! empty( $filters['page_id'] ) && is_array( $filters['page_id'] ) ) {
$where .= ' AND page_id IN (' . implode( ',', $filters['page_id'] ) . ')';
}
$sql .= $where;
$sql = str_replace( "'", '', $sql );
$sql = $this->prepare( $sql, $params );
return $this->wpdb->query( $sql );
}
public function get_variation( $id ) {
$params = array( $id );
$where = ' `id`=%d ';
$sql = 'SELECT * FROM ' . tqb_table_name( 'variations' ) . ' WHERE ' . $where;
return $this->wpdb->get_row( $this->prepare( $sql, $params ), ARRAY_A );
}
/**
* Saves a test in the database
*
* @param $model
*
* @return bool|int
*/
public function save_test( $model ) {
/* make sure that we have an array */
if ( is_object( $model ) ) {
$model = get_object_vars( $model );
}
$_columns = array(
'id',
'page_id',
'date_started',
'date_added',
'date_completed',
'config',
'status',
'conversion_goal',
'title',
'notes',
'auto_win_enabled',
'auto_win_min_conversions',
'auto_win_min_duration',
'auto_win_chance_original',
);
$data = array();
foreach ( $_columns as $key ) {
if ( isset( $model[ $key ] ) ) {
$data[ $key ] = $model[ $key ];
}
}
if ( ! empty( $data['id'] ) ) {
$update_rows = $this->wpdb->update( tqb_table_name( 'tests' ), $data, array( 'id' => $data['id'] ) );
return $update_rows !== false;
}
$this->wpdb->insert( tqb_table_name( 'tests' ), $data );
return $this->wpdb->insert_id;
}
/**
* Saves a test item in the database
*
* @param $model
*
* @return bool|int
*/
public function save_test_item( $model ) {
/* make sure that we have an array */
if ( is_object( $model ) ) {
$model = get_object_vars( $model );
}
$_columns = array(
'id',
'test_id',
'variation_id',
'variation_title',
'is_control',
'is_winner',
'impressions',
'optins_conversions',
'social_shares',
'active',
'stopped_date',
);
$data = array();
foreach ( $_columns as $key ) {
if ( isset( $model[ $key ] ) ) {
$data[ $key ] = $model[ $key ];
}
}
unset( $model );
if ( ! empty( $data['is_winner'] ) ) {
$test_model = $this->get_test( array( 'id' => $data['test_id'] ), true, ARRAY_A );
$stop_test = $this->stop_test( $test_model );
$archive_variations = $this->archive_losing_variations( $test_model, $data['variation_id'] );
$set_winner = $this->save_variation( array( 'id' => $data['variation_id'], 'is_control' => 1 ) );
$test = $this->get_test( array( 'id' => $data['test_id'] ), true, OBJECT );
$test->url = admin_url( 'admin.php?page=tqb_admin_dashboard' ) . '#dashboard/test/' . $test->id;
$test->trigger_source = 'tqb';
$test_item = $this->get_test_items( array( 'id' => $data['id'] ), OBJECT );
$test_item->variation = $this->get_variation( $test_item->variation_id );
$test_item->variation['key'] = $test_item->variation['id'];
do_action( 'tqb_split_test_ends', $test_item, $test );
}
if ( isset( $data['active'] ) && $data['active'] == 0 ) {
$data['stopped_date'] = date( 'Y-m-d H:i:s' );
$stopped = $this->stop_test_if_no_items_left( $data );
if ( $stopped ) {
return true;
}
}
if ( ! empty( $data['id'] ) ) {
$update_rows = $this->wpdb->update( tqb_table_name( 'tests_items' ), $data, array( 'id' => $data['id'] ) );
return $update_rows !== false;
}
$this->wpdb->insert( tqb_table_name( 'tests_items' ), $data );
$id = $this->wpdb->insert_id;
return $id;
}
/**
* Update test item if variation was displayed/acted upon
*
* @param $data
*
* @return bool|int
*/
public function update_test_item_action_counter( $data ) {
if ( empty( $data['variation_id'] ) ) {
return false;
}
$fields = '';
$params = array();
if ( ! empty( $data['impression'] ) ) {
$fields .= ' impressions = impressions + 1, social_shares = social_shares + 1 ';
}
if ( ! empty( $data['conversion'] ) ) {
$fields .= ' optins_conversions = optins_conversions + 1 ';
}
if ( ! empty( $data['social_shares_conversions'] ) ) {
$fields .= ' social_shares_conversions = social_shares_conversions + 1 ';
}
$where = ' 1 ';
$params ['active'] = 1;
$where .= ' AND `active`=%d ';
if ( ! empty( $data['variation_id'] ) ) {
$params ['variation_id'] = $data['variation_id'];
$where .= ' AND `variation_id`=%d ';
}
if ( ! empty( $data['test_id'] ) ) {
$params ['test_id'] = $data['test_id'];
$where .= ' AND `test_id`=%d ';
}
$sql = 'UPDATE ' . tqb_table_name( 'tests_items' ) . ' SET ' . $fields . ' WHERE ' . $where;
return $this->wpdb->query( $this->wpdb->prepare( $sql, $params ) );
}
/**
* Update test item if variation was displayed/acted upon
*
* @param $data
*
* @return bool|int
*/
public function update_variation_cached_counter( $data ) {
if ( empty( $data['variation_id'] ) ) {
return false;
}
$fields = '';
if ( ! empty( $data['impression'] ) ) {
$fields .= ' cache_impressions = cache_impressions + 1, cache_social_shares = cache_social_shares + 1 ';
}
if ( ! empty( $data['conversion'] ) ) {
$fields .= ' cache_optins_conversions = cache_optins_conversions + 1 ';
}
if ( ! empty( $data['social_conversion'] ) ) {
$fields .= ' cache_social_shares_conversions = cache_social_shares_conversions + 1 ';
}
$where = ' `id`= %d';
$sql = 'UPDATE ' . tqb_table_name( 'variations' ) . ' SET ' . $fields . ' WHERE ' . $where;
return $this->wpdb->query( $this->wpdb->prepare( $sql, array( 'id' => $data['variation_id'] ) ) );
}
/**
* Archive losing variations
*
* @param array $test_model
* @param array $winner_id
*
* @return false|int
*/
public function archive_losing_variations( $test_model, $winner_id ) {
$test_items = $this->get_test_items( array( 'test_id' => $test_model['id'] ) );
foreach ( $test_items as $test_item ) {
if ( $test_item['variation_id'] != $winner_id ) {
$variation = $this->get_variation( $test_item['variation_id'] );
$variation['post_status'] = 'archive';
$variation['is_control'] = 0;
unset( $variation['tcb_fields'] );
$variation = $this->save_variation( $variation );
}
}
return true;
}
/**
* Delete tests
*
* @param array $filters
*
* @return false|int
*/
public function delete_tests( $filters = array() ) {
$params = array();
if ( ! empty( $filters['id'] ) ) {
$params ['id'] = $filters['id'];
}
if ( ! empty( $filters['page_id'] ) ) {
$params ['page_id'] = $filters['page_id'];
}
if ( ! empty( $params ) ) {
$this->delete_page_test_items( $params );
return $this->wpdb->delete( tqb_table_name( 'tests' ), $params );
}
return false;
}
/**
* Delete test items belonging to page
*
* @param array $filters
*
* @return false|int
*/
public function delete_page_test_items( $filters = array() ) {
if ( ! empty( $filters['id'] ) ) {
$params ['test_id'] = $filters['id'];
return $this->delete_test_items( $params );
}
if ( ! empty( $filters['page_id'] ) ) {
$params ['page_id'] = $filters['page_id'];
$prepared_statement = $this->wpdb->prepare( 'SELECT id FROM ' . tqb_table_name( 'tests' ) . ' WHERE page_id = %d', $filters['page_id'] );
$tests = $this->wpdb->get_col( $prepared_statement );
foreach ( $tests as $test ) {
$params ['test_id'] = $test;
$this->delete_test_items( $params );
}
}
return true;
}
/**
* Delete test items
*
* @param array $filters
*
* @return false|int
*/
public function delete_test_items( $filters = array() ) {
$params = array();
if ( ! empty( $filters['test_id'] ) ) {
$params ['test_id'] = $filters['test_id'];
}
if ( ! empty( $params ) ) {
return $this->wpdb->delete( tqb_table_name( 'tests_items' ), $params );
}
return false;
}
/**
* Stop test if no items left
*
* @param array $model
*
* @return false|int
*/
public function stop_test_if_no_items_left( $model ) {
$test_items = $this->get_test_items( array( 'test_id' => $model['test_id'], 'active' => 1 ) );
if ( count( $test_items ) < 3 ) {
foreach ( $test_items as $item ) {
if ( $item['id'] !== $model['id'] ) {
$this->set_winner( $item );
}
}
$test = $this->get_test( array( 'id' => $model['test_id'] ), true );
$this->stop_test( $test );
return true;
}
return false;
}
/**
* Stop test
*
* @param array $test
*
* @return false|int
*/
public function stop_test( $test ) {
$test['status'] = 0;
$test['date_completed'] = date( 'Y-m-d H:i:s' );
return $this->save_test( $test );
}
/**
* Set winner
*
* @param array $item
*
* @return false|int
*/
public function set_winner( $item ) {
$item['is_winner'] = 1;
return $this->save_test_item( $item );
}
/**
* Returns a count of event_types from a group in a time period
*
* @param $filter Array of filters for the result
*
* @return Array with number of conversions per group_id in a period of time
*/
public function get_report_data_count_event_type( $filter ) {
$date_interval = '';
switch ( $filter['interval'] ) {
case 'month':
$date_interval = 'CONCAT(MONTHNAME(`log`.`date`)," ", YEAR(`log`.`date`)) as date_interval';
break;
case 'week':
$year = 'IF( WEEKOFYEAR(`log`.`date`) = 1 AND MONTH(`log`.`date`) = 12, 1 + YEAR(`log`.`date`), YEAR(`log`.`date`) )';
$date_interval = "CONCAT('Week ', WEEKOFYEAR(`log`.`date`), ', ', {$year}) as date_interval";
break;
case 'day':
$date_interval = 'DATE(`log`.`date`) as date_interval';
break;
}
$sql = 'SELECT IFNULL(COUNT( DISTINCT log.id ), 0) AS log_count, event_type, log.' . $filter['data_group'] . ' AS data_group, ' . $date_interval;
if ( ! empty( $filter['unique_email'] ) && $filter['unique_email'] == 1 ) {
/* count if this email is added for the first time. if so, this is a lead, else it's just a simple conversion */
$sql .= ', SUM( IF( t_log.id IS NOT NULL , 1, 0) ) AS leads ';
}
$sql .= ' FROM ' . tqb_table_name( 'event_log' ) . ' AS `log` ';
if ( ! empty( $filter['unique_email'] ) && $filter['unique_email'] == 1 ) {
/* t_logs - temporary select to see if an email is added for the first time or not */
$sql .= ' LEFT JOIN (SELECT user, MIN(id) AS id FROM ' . tqb_table_name( 'event_log' ) . ' GROUP BY user) AS t_log ON log.user=t_log.user AND log.id=t_log.id ';
}
$sql .= ' WHERE 1 ';
$params = array();
if ( ! empty( $filter['event_type'] ) ) {
$sql .= 'AND `event_type` = %d ';
$params [] = $filter['event_type'];
}
if ( ! empty( $filter['variation_id'] ) ) {
$sql .= 'AND `variation_id` = %d ';
$params [] = $filter['variation_id'];
}
if ( ! empty( $filter['conversion_goal'] ) ) {
// Only apply conversion goal filter to conversion events (event_type = 2)
// Impressions (event_type = 1) should not be filtered by optin/social_share
if ( $filter['conversion_goal'] === Thrive_Quiz_Builder::CONVERSION_GOAL_SOCIAL ) {
$sql .= 'AND (`event_type` = 1 OR (`event_type` = 2 AND `social_share` = 1)) ';
} else {
$sql .= 'AND (`event_type` = 1 OR (`event_type` = 2 AND `optin` = 1)) ';
}
}
if ( ! empty( $filter['page_id'] ) ) {
$sql .= 'AND `page_id` = %d ';
$params [] = $filter['page_id'];
}
if ( ! empty( $filter['start_date'] ) && ! empty( $filter['end_date'] ) ) {
$timezone_diff = current_time( 'timestamp' ) - time();
$sql .= 'AND `date` BETWEEN %s AND %s ';
$params [] = $filter['start_date'];
$params [] = date( 'Y-m-d H:i:s', ( strtotime( '+1 day', strtotime( $filter['end_date'] ) ) + $timezone_diff ) );
}
if ( ! empty( $filter['group_by'] ) && count( $filter['group_by'] ) > 0 ) {
$sql .= 'GROUP BY ' . implode( ', ', $filter['group_by'] );
}
$sql .= ' ORDER BY `log`.`date` DESC';
return $this->wpdb->get_results( $this->prepare( $sql, $params ) );
}
public function create_event_log_entry( $model ) {
// Check if stats tracking is enabled
if ( ! tqb_is_stats_tracking_enabled() ) {
return false;
}
$_columns = array(
'date',
'event_type',
'variation_id',
'page_id',
'post_id',
'user_unique',
'optin',
'social_share',
'duplicate',
);
$data = array();
foreach ( $_columns as $key ) {
if ( isset( $model[ $key ] ) ) {
$data[ $key ] = $model[ $key ];
}
}
unset( $model );
$params = array( 'user_unique' => $data['user_unique'], 'event_type' => $data['event_type'], 'page_id' => $data['page_id'] );
$where = ' AND `user_unique`=%s AND `event_type`=%d AND `page_id`=%d';
$sql = 'SELECT * FROM ' . tqb_table_name( 'event_log' ) . " WHERE 1 {$where}";
$event_log = $this->wpdb->get_row( $this->prepare( $sql, $params ), ARRAY_A );
if ( ! empty( $event_log ) ) {
$update_row = $this->wpdb->update( tqb_table_name( 'event_log' ), $data, array( 'id' => $event_log['id'] ) );
// Clear statistics cache when event is updated (social shares, subscribers)
if ( ! empty( $data['page_id'] ) ) {
$page = get_post( $data['page_id'] );
if ( $page && ! empty( $page->post_parent ) ) {
TQB_Quiz_Manager::clear_quiz_statistics_cache( $page->post_parent );
}
}
return $event_log;
}
$result = $this->wpdb->insert( tqb_table_name( 'event_log' ), $data );
// Clear statistics cache when new event is created (social shares, subscribers)
if ( $result && ! empty( $data['page_id'] ) ) {
$page = get_post( $data['page_id'] );
if ( $page && ! empty( $page->post_parent ) ) {
TQB_Quiz_Manager::clear_quiz_statistics_cache( $page->post_parent );
}
}
return $result;
}
public function get_quiz_user( $unique, $quiz_id ) {
// Request-level cache key
$cache_key = md5( $unique . '_' . $quiz_id );
// Check request-level cache first
if ( isset( self::$user_cache[ $cache_key ] ) ) {
return self::$user_cache[ $cache_key ];
}
$params = array( 'random_identifier' => $unique, 'quiz_id' => $quiz_id );
$where = ' AND `random_identifier`=%s AND `quiz_id`=%d';
$sql = 'SELECT * FROM ' . tqb_table_name( 'users' ) . " WHERE 1 {$where}";
$user = $this->wpdb->get_row( $this->prepare( $sql, $params ), ARRAY_A );
// Cache result (even if null/empty)
self::$user_cache[ $cache_key ] = $user;
return $user;
}
/**
* save a quiz user into the database
*
* @param array $model
*
* @return int
*/
public function save_quiz_user( $model = array() ) {
$_columns = array(
'id',
'quiz_id',
'random_identifier',
'social_badge_link',
'email',
'points',
'quiz_id',
'completed_quiz',
'ignore_user',
'wp_user_id',
'object_id',
);
$data = array();
foreach ( $_columns as $key ) {
if ( isset( $model[ $key ] ) ) {
$data[ $key ] = $model[ $key ];
}
}
// If stats tracking is disabled and this is a completion update, skip it.
if ( ! tqb_is_stats_tracking_enabled() && ! empty( $data['completed_quiz'] ) && ! empty( $data['id'] ) ) {
return $data['id'];
}
if ( ! isset( $data['wp_user_id'] ) && is_user_logged_in() ) {
$data['wp_user_id'] = get_current_user_id();
}
if ( ! empty( $data['completed_quiz'] ) ) {
$data['date_finished'] = date( 'Y-m-d H:i:s' );
}
if ( ! empty( $data['id'] ) ) {
$update_rows = $this->wpdb->update( tqb_table_name( 'users' ), $data, array( 'id' => $data['id'] ) );
if ( $update_rows !== false ) {
// Clear statistics cache when user completes quiz
if ( ! empty( $data['quiz_id'] ) ) {
TQB_Quiz_Manager::clear_quiz_statistics_cache( $data['quiz_id'] );
}
// Clear cache for this user (if we have the unique identifier)
if ( ! empty( $data['random_identifier'] ) && ! empty( $data['quiz_id'] ) ) {
$cache_key = md5( $data['random_identifier'] . '_' . $data['quiz_id'] );
unset( self::$user_cache[ $cache_key ] );
}
return $data['id'];
}
return $update_rows;
}
$data['date_started'] = date( 'Y-m-d H:i:s' );
$this->wpdb->insert( tqb_table_name( 'users' ), $data );
$user_id = $this->wpdb->insert_id;
// Clear statistics cache when new user starts quiz
if ( ! empty( $data['quiz_id'] ) ) {
TQB_Quiz_Manager::clear_quiz_statistics_cache( $data['quiz_id'] );
}
// Cache the newly created user without triggering an extra DB query
if ( ! empty( $data['random_identifier'] ) && ! empty( $data['quiz_id'] ) && $user_id ) {
$cache_key = md5( $data['random_identifier'] . '_' . $data['quiz_id'] );
$user = $data;
$user['id'] = $user_id;
self::$user_cache[ $cache_key ] = $user;
}
return $user_id;
}
/**
* save a quiz user's answer
*
* @param array $model
*
* @return int
*/
public function save_user_answer( $model = array() ) {
$_columns = array(
'id',
'quiz_id',
'user_id',
'answer_id',
'question_id',
'answer_text',
);
$data = array();
foreach ( $_columns as $key ) {
if ( isset( $model[ $key ] ) ) {
$data[ $key ] = $model[ $key ];
}
}
unset( $model );
// If stats tracking is disabled, skip saving answers (but return success for quiz to continue).
if ( ! tqb_is_stats_tracking_enabled() ) {
return ! empty( $data['id'] ) ? $data['id'] : 1;
}
if ( ! empty( $data['id'] ) ) {
$update_rows = $this->wpdb->update( tqb_table_name( 'user_answers' ), $data, array( 'id' => $data['id'] ) );
if ( $update_rows !== false ) {
return $data['id'];
}
return $update_rows;
}
$this->wpdb->insert( tqb_table_name( 'user_answers' ), $data );
$answer_id = $this->wpdb->insert_id;
return $answer_id;
}
/**
* generate dummy data for tests
*
* @return false|int
*/
public function generate_dummy_data( $test_id, $entry_count, $min_date, $max_date ) {
$test_items = $this->get_test_items( array( 'test_id' => $test_id ) );
$test = $this->get_test( array( 'id' => $test_id ), true );
for ( $i = 1; $i <= $entry_count; $i ++ ) {
$random = rand( 1, ( count( $test_items ) ) );
$data['date'] = $this->rand_date( $min_date, $max_date );
$data['event_type'] = ( rand( 1, 3 ) % 2 ) == 0 ? 2 : 1;
$data['variation_id'] = $test_items[ $random - 1 ]['variation_id'];
$data['user'] = 'dummy@dummy.dumb';
$data['page_id'] = $test['page_id'];
$this->wpdb->insert( tqb_table_name( 'event_log' ), $data );
$field_type = $data['event_type'] == 1 ? 'impressions' : 'optins_conversions';
$test_items[ $random - 1 ][ $field_type ] ++;
$this->wpdb->update(
tqb_table_name( 'tests_items' ),
array( $field_type => ( $test_items[ $random - 1 ][ $field_type ] ) ),
array(
'id' => $test_items[ $random - 1 ]['id'],
)
);
}
}
public function rand_date( $min_date, $max_date ) {
$min_epoch = strtotime( $min_date );
$max_epoch = strtotime( $max_date );
$rand_epoch = rand( $min_epoch, $max_epoch );
return date( 'Y-m-d H:i:s', $rand_epoch );
}
/**
* Delete all the results from DB based on quiz_id
*
* @param array $filters
*
* @return false|int
*/
public function delete_quiz_results( $filters = array() ) {
return $this->wpdb->delete( tqb_table_name( 'results' ), $filters );
}
/**
* Delete quiz users
*
* @param array $filters
*
* @return false|int
*/
public function delete_quiz_users( $filters = array() ) {
$params = array();
if ( ! empty( $filters['quiz_id'] ) ) {
$params ['quiz_id'] = $filters['quiz_id'];
}
if ( ! empty( $params ) ) {
return $this->wpdb->delete( tqb_table_name( 'users' ), $filters );
}
return false;
}
/**
* Deletes user answers
*
* @param array $filters
*
* @return bool|false|int
*/
public function delete_user_answers( $filters = array() ) {
$params = array();
if ( ! empty( $filters['quiz_id'] ) ) {
$params ['quiz_id'] = $filters['quiz_id'];
}
if ( ! empty( $filters['user_id'] ) ) {
$params ['user_id'] = $filters['user_id'];
}
if ( ! empty( $filters['question_id'] ) ) {
$params ['question_id'] = $filters['question_id'];
}
if ( ! empty( $params ) ) {
return $this->wpdb->delete( tqb_table_name( 'user_answers' ), $params );
}
return false;
}
/**
* Insert results into DB
*
* @param int $quiz_id
* @param array $results
*
* @return array
*/
public function save_quiz_results( $quiz_id, $results ) {
$return = array();
foreach ( $results as $result ) {
if ( empty( $result['id'] ) ) {
$inserted = $this->insert_new_quiz_result( $quiz_id, $result['text'] );
} else {
$this->wpdb->update( tqb_table_name( 'results' ), array( 'text' => $result['text'] ), array( 'id' => $result['id'] ) );
$inserted = $result['id'];
}
$return[] = array(
'id' => $inserted,
'quiz_id' => $quiz_id,
'text' => $result['text'],
);
}
return $return;
}
/**
* Add new Result for a Quiz
*
* @param int $quiz_id
* @param string $result
*
* @return int new result id
*/
public function insert_new_quiz_result( $quiz_id, $result ) {
$this->wpdb->insert( tqb_table_name( 'results' ), array(
'quiz_id' => $quiz_id,
'text' => $result,
) );
return $this->wpdb->insert_id;
}
/**
* Get and array with quiz results from DB
*
* @param $quiz_id
*
* @return array|null
*/
public function get_quiz_results( $quiz_id ) {
if ( ! isset( $this->wpdb ) || ! is_object( $this->wpdb ) ) {
return array();
}
$where = ' WHERE quiz_id = %d';
$params['quiz_id'] = $quiz_id;
$sql = 'SELECT * FROM ' . tqb_table_name( 'results' ) . $where . ' ORDER BY id';
$sql = $this->prepare( $sql, $params );
$results = $this->wpdb->get_results( $sql, ARRAY_A );
return is_array( $results ) ? $results : array();
}
/**
* Get and array with quiz results from DB
*
* @param $result_id
*
* @return array|null
*/
public function get_quiz_results_single( $result_id ) {
$where = ' WHERE id = %d';
$params['id'] = $result_id;
$sql = 'SELECT * FROM ' . tqb_table_name( 'results' ) . $where;
$sql = $this->prepare( $sql, $params );
return $this->wpdb->get_row( $sql, ARRAY_A );
}
/**
* Get explicit result
*
* @param $points
*
* @return int|string
*/
public function get_explicit_result( $points ) {
if ( ! empty( $points['result_id'] ) ) {
$result = $this->get_quiz_results_single( $points['result_id'] );
if ( ! empty( $result['text'] ) ) {
return $result['text'];
}
}
if ( isset( $points['max_points'] ) && isset( $points['min_points'] ) ) {
$range = $points['max_points'] - $points['min_points'];
if ( ! $range ) {
$result_percent = 100;
} else {
$result_percent = max( ( (int) $points['user_points'] - $points['min_points'] ), 0 ) * 100 / $range;
}
if ( isset( $points['quiz_completed'] ) && ! $points['quiz_completed'] ) {
return 'incomplete';
}
return ( round( $result_percent, 2 ) . $points['extra'] );
}
if ( isset( $points['quiz_type'] ) && $points['quiz_type'] === Thrive_Quiz_Builder::QUIZ_TYPE_RIGHT_WRONG ) {
$processed = isset( $points['total_questions'] ) && isset( $points['total_valid_questions'] ) ? $points['total_valid_questions'] . '/' . $points['total_questions'] : '';
return ! empty( $processed ) ? $processed : $points['user_points'];
}
return $points['user_points'];
}
/**
* Get completed quiz count from DB
*
* @param $quiz_id
* @param array $filters
*
* @return string|null
*/
public function get_completed_quiz_count( $quiz_id, $filters = [] ) {
$where = ' WHERE quiz_id = %d AND completed_quiz = 1 AND ignore_user IS NULL ';
if ( isset( $filters['since']['date'] ) ) {
$where .= " AND date_started > '" . $this->wordpress_to_server_date( $filters['since']['date'] ) . "'";
}
if ( ! empty( $filters['location'] ) ) {
$where .= ' AND object_id = ' . esc_sql( $filters['location'] );
}
$params['quiz_id'] = $quiz_id;
$sql = 'SELECT COUNT(*) FROM ' . tqb_table_name( 'users' ) . $where;
$sql = $this->prepare( $sql, $params );
return $this->wpdb->get_var( $sql );
}
public function wordpress_to_server_date( $date ) {
$timezone_diff = current_time( 'timestamp' ) - time();
$date = date( 'Y-m-d H:i:s', ( strtotime( $date ) - $timezone_diff ) );
return $date;
}
/**
* Build the sql for selecting data for the flow report based on the filters
*
* @param $page_id
* @param array $filters
* @param string $where
*
* @return false|string|null
*/
private function build_sql_for_flow_related_data( $page_id, $filters, $where ) {
$join = '';
if ( isset( $filters['since']['date'] ) ) {
$where .= " AND date > '" . $this->wordpress_to_server_date( $filters['since']['date'] ) . "'";
}
if ( ! empty( $filters['location'] ) && empty ( $filters['no_splash'] ) ) {
$join = ' INNER JOIN ' . tqb_table_name( 'users' ) . ' AS users ON event_log.user_unique = users.random_identifier';
$where .= ' AND users.object_id = ' . esc_sql( $filters['location'] );
} else if ( ! empty( $filters['location'] ) && ! empty ( $filters['no_splash'] ) ) {
/**
* if there is no splash page we have to count all the impressions on a qna page and display it for each location
*/
$where .= ' AND event_log.post_id = ' . esc_sql( $filters['location'] );
}
$where .= ' GROUP BY event_type';
$params['page_id'] = $page_id;
$sql = 'SELECT IFNULL(COUNT(*), 0) as count, event_type FROM ' . tqb_table_name( 'event_log' ) . ' AS event_log' . $join . $where;
return $this->prepare( $sql, $params );
}
/**
* Get flow data from DB
*
* @param $page_id
* @param $filters
*
* @return array|null
*/
public function get_flow_data( $page_id, $filters ) {
$where = ' WHERE page_id = %d ';
$sql = $this->build_sql_for_flow_related_data( $page_id, $filters, $where );
$result = $this->wpdb->get_results( $sql );
$standard = array(
Thrive_Quiz_Builder::TQB_IMPRESSION,
Thrive_Quiz_Builder::TQB_CONVERSION,
Thrive_Quiz_Builder::TQB_SKIP_OPTIN,
);
$data = array();
foreach ( $standard as $event_type ) {
$data[ $event_type ] = 0;
foreach ( $result as $event ) {
if ( $event->event_type == $event_type ) {
$data[ $event_type ] = $event->count;
}
}
}
return $data;
}
/**
* Get flow data from DB
*
* @param $page_id
* @param $filters
*
* @return array|null
*/
public function get_flow_splash_impressions( $page_id, $filters ) {
$where = ' WHERE page_id = %d ';
if ( isset( $filters['since']['date'] ) ) {
$where .= " AND date > '" . $this->wordpress_to_server_date( $filters['since']['date'] ) . "'";
}
if ( ! empty( $filters['location'] ) ) {
$where .= ' AND post_id = ' . esc_sql( $filters['location'] );
}
$where .= ' AND event_type = 1';
$params['page_id'] = $page_id;
$sql = 'SELECT IFNULL(COUNT(*), 0) as count, event_type FROM ' . tqb_table_name( 'event_log' ) . ' AS event_log' . $where;
$sql = $this->prepare( $sql, $params );
$result = $this->wpdb->get_results( $sql );
$data = array();
$data[ Thrive_Quiz_Builder::TQB_IMPRESSION ] = $result[0]->count;
return $data;
}
/**
* Get page subscribers
*
* @param $page_id
* @param array $filters
*
* @return array|null
*/
public function get_page_subscribers( $page_id, $filters = [] ) {
$where = ' WHERE page_id = %d AND event_type = 2 AND optin = 1 ';
$sql = $this->build_sql_for_flow_related_data( $page_id, $filters, $where );
return $this->wpdb->get_var( $sql );
}
/**
* Get social shares for results page
*
* @param $page_id
* @param $filters
*
* @return array|null
*/
public function get_page_social_shares( $page_id, $filters ) {
$where = ' WHERE page_id = %d AND event_type = 2 AND social_share = 1 ';
$sql = $this->build_sql_for_flow_related_data( $page_id, $filters, $where );
return $this->wpdb->get_var( $sql );
}
/**
* Get quiz social share count from DB
*
* @param $quiz_id
*
* @return array|null
*/
public function get_quiz_social_shares_count( $quiz_id ) {
$results_page = get_posts( array( 'post_parent' => $quiz_id, 'post_type' => Thrive_Quiz_Builder::QUIZ_STRUCTURE_ITEM_RESULTS ) );
if ( empty( $results_page[0] ) ) {
return 0;
}
$where = ' WHERE page_id = %d AND social_share = 1';
$params['page_id'] = $results_page[0]->ID;
$sql = 'SELECT COUNT(*) FROM ' . tqb_table_name( 'event_log' ) . $where;
$sql = $this->prepare( $sql, $params );
return $this->wpdb->get_var( $sql );
}
/**
* Get total quiz users count from DB
*
* @param $quiz_id
* @param $completed_quiz
*
* @return array|null
*/
public function get_quiz_users_count( $quiz_id, $completed_quiz = false ) {
$where = ' WHERE quiz_id = %d AND ignore_user IS NULL ';
if ( $completed_quiz ) {
$where .= 'AND completed_quiz=1 ';
}
$params['quiz_id'] = $quiz_id;
$sql = 'SELECT COUNT(*) FROM ' . tqb_table_name( 'users' ) . $where;
$sql = $this->prepare( $sql, $params );
return $this->wpdb->get_var( $sql );
}
/**
* Get filtered quiz users count from DB
*
* @param int $quiz_id
* @param array $completed_quiz
*
* @return array|null
*/
public function get_filtered_users_count( $quiz_id, $params = array() ) {
$select_users = $this->get_sql_for_quiz_users( $quiz_id, $params );
$order = ' ORDER BY id DESC ';
if ( ! empty( $params['per_page'] ) && is_numeric( $params['per_page'] ) ) {
$order .= ' LIMIT ' . $params['per_page'];
if ( ! empty( $params['offset'] ) && is_numeric( $params['offset'] ) ) {
$order .= ' OFFSET ' . $params['offset'];
}
}
$sql = 'SELECT COUNT(users.id) AS total_items FROM (' . $select_users . ') AS users' . $order;
return $this->wpdb->get_results( $sql );
}
/**
* Get quiz users from DB based on the filters set
*
* @param $quiz_id
* @param $params
*
* @return array|null
*/
public function get_quiz_users( $quiz_id, $params = array() ) {
$select_users = $this->get_sql_for_quiz_users( $quiz_id, $params );
$order = ' ORDER BY id DESC ';
if ( ! empty( $params['per_page'] ) && is_numeric( $params['per_page'] ) ) {
$order .= ' LIMIT ' . $params['per_page'];
if ( ! empty( $params['offset'] ) && is_numeric( $params['offset'] ) ) {
$order .= ' OFFSET ' . $params['offset'];
}
}
$sql = $select_users . $order;
return $this->wpdb->get_results( $sql );
}
/**
* Returns sql for filtering users
*
* @param int $quiz_id
* @param array $params
*
* @return string
*/
protected function get_sql_for_quiz_users( $quiz_id, $params = array() ) {
$where_guest = ' WHERE quiz_id = ' . $quiz_id . ' AND ignore_user IS NULL AND wp_user_id = 0 ';
$where_user = ' WHERE quiz_id = ' . $quiz_id . ' AND ignore_user IS NULL AND wp_user_id != 0 ';
$where = '';
if ( ! empty( $params['progress'] ) ) {
switch ( $params['progress'] ) {
case 'in_progress':
$where .= 'AND completed_quiz IS NULL ';
break;
case 'completed':
$where .= 'AND completed_quiz=1 ' . $this->get_sql_for_results_filtering( $params );
break;
case 'all':
default:
break;
}
}
if ( ! empty( $params['date_started'] ) ) {
$where .= 'AND date_started >= "' . esc_sql( $params['date_started'] ) . '" ';
}
if ( ! empty( $params['date_finished'] ) ) {
$where .= 'AND DATE(date_started) <= "' . esc_sql( $params['date_finished'] ) . '" ';
}
if ( ! empty( $params['location'] ) ) {
$where .= 'AND object_id = "' . esc_sql( $params['location'] ) . '" ';
}
$select_guest_users = 'SELECT *, 1 as nb_of_tries FROM ' . tqb_table_name( 'users' ) . $where_guest . $where;
$select_latest_start_dates = 'SELECT wp_user_id, COUNT(id) as nb_of_tries, MAX(date_started) as date_started FROM ' . tqb_table_name( 'users' )
. $where_user . $where . ' GROUP BY wp_user_id';
$select_users = 'SELECT ' . tqb_table_name( 'users' ) . '.*, latest_completions.nb_of_tries FROM ' . tqb_table_name( 'users' )
. ' INNER JOIN ( ' . $select_latest_start_dates . ' ) latest_completions ON '
. tqb_table_name( 'users' ) . '.wp_user_id = latest_completions.wp_user_id AND '
. tqb_table_name( 'users' ) . '.date_started = latest_completions.date_started';
return $select_guest_users . ' UNION ' . $select_users;
}
/**
* Returns sql for a WHERE clause used for filtering quiz users based on points
*
* @param array $params
*
* @return string
*/
protected function get_sql_for_results_filtering( $params ) {
$where = '';
switch ( $params['quiz_type'] ) {
case 'number':
if ( ! empty( $params['result_min'] ) ) {
$where .= 'AND points >=' . esc_sql( $params['result_min'] ) . ' ';
}
if ( ! empty( $params['result_max'] ) ) {
$where .= 'AND points <=' . esc_sql( $params['result_max'] ) . ' ';
}
break;
case 'percentage':
if ( ! empty( $params['result_min'] ) ) {
$where .= 'AND SUBSTRING_INDEX(points, "%", 1) >=' . esc_sql( $params['result_min'] ) . ' ';
}
if ( ! empty( $params['result_max'] ) ) {
$where .= 'AND SUBSTRING_INDEX(points, "%", 1) <=' . esc_sql( $params['result_max'] ) . ' ';
}
break;
case 'right_wrong':
if ( ! empty( $params['result_min'] ) ) {
$where .= 'AND SUBSTRING_INDEX(points, "/", 1) >=' . esc_sql( $params['result_min'] ) . ' ';
}
if ( ! empty( $params['result_max'] ) ) {
$where .= 'AND SUBSTRING_INDEX(points, "/", 1) <=' . esc_sql( $params['result_max'] ) . ' ';
}
break;
case 'personality':
if ( ! empty( $params['categories'] ) ) {
$numCat = count( $params['categories'] ) - 1;
$where .= 'AND ( ';
foreach ( $params['categories'] as $key => $category ) {
$where .= 'points="' . esc_sql( $category ) . '"';
$where .= $key === $numCat ? ' ) ' : ' OR ';
}
}
break;
default:
break;
}
return $where;
}
/**
* Returns quiz users from the database
*
* @param array $filters
* @param string $return_type
*
* @return array|null|object
*/
public function get_users( $filters = array(), $return_type = ARRAY_A ) {
$params = array();
$sql = 'SELECT * FROM ' . tqb_table_name( 'users' ) . ' WHERE 1 ';
if ( ! empty( $filters['email'] ) ) {
$sql .= ' AND email = %s';
$params [] = $filters['email'];
}
if ( ! empty( $filters['quiz_id'] ) ) {
$sql .= ' AND quiz_id = %s';
$params [] = $filters['quiz_id'];
}
if ( isset( $filters['completed_quiz'] ) && is_numeric( $filters['completed_quiz'] ) ) {
if ( $filters['completed_quiz'] ) {
$sql .= ' AND completed_quiz = 1';
} else {
$sql .= ' AND completed_quiz IS NULL';
}
}
if ( ! empty( $filters['id'] ) ) {
$sql .= ' AND id = %d';
$params [] = $filters['id'];
}
if ( ! empty( $filters['wp_user_id'] ) ) {
$sql .= ' AND wp_user_id = %d';
$params [] = $filters['wp_user_id'];
}
if ( ! empty( $filters['random_identifier'] ) ) {
$sql .= ' AND random_identifier = %s';
$params [] = $filters['random_identifier'];
}
if ( ! empty( $filters['object_id'] ) ) {
$sql .= ' AND object_id = %d';
$params [] = $filters['object_id'];
}
if ( ! empty( $filters['order_by'] ) && ! empty( $filters['order_direction'] ) ) {
$sql .= ' ORDER BY ' . $this->wpdb->_escape( $filters['order_by'] ) . ' ' . $this->wpdb->_escape( $filters['order_direction'] );
}
if ( ! empty( $filters['limit'] ) && is_numeric( $filters['limit'] ) ) {
$sql .= ' LIMIT %d ';
$params [] = $filters['limit'];
}
$sql = $this->prepare( $sql, $params );
return $this->wpdb->get_results( $sql, $return_type );
}
/**
* Get quiz user answer from DB
*
* @param $params
*
* @return array|null
*/
public function get_user_answers( $params = array() ) {
if ( empty( $params['quiz_id'] ) || empty( $params['user_id'] ) ) {
return false;
}
$where = ' WHERE quiz_id = %d AND user_id =%d ';
$data['quiz_id'] = $params['quiz_id'];
$data['user_id'] = $params['user_id'];
if ( ! empty( $params['question_id'] ) ) {
$where .= ' AND question_id=%d';
$data['question_id'] = $params['question_id'];
}
if ( ! empty( $params['answer_id'] ) ) {
$where .= ' AND answer_id=%d';
$data['answer_id'] = $params['answer_id'];
}
if ( ! empty( $params['limit'] ) ) {
$where .= ' LIMIT %d';
$data['limit'] = $params['limit'];
}
$sql = 'SELECT * FROM ' . tqb_table_name( 'user_answers' ) . $where;
$sql = $this->prepare( $sql, $data );
return ! empty( $params['limit'] ) && $params['limit'] === 1 ? $this->wpdb->get_row( $sql, ARRAY_A ) : $this->wpdb->get_results( $sql, ARRAY_A );
}
/**
* Get existing user answers in bulk for duplicate checking
* Returns array of answer_ids that already exist for the given user and quiz
*
* @param int $quiz_id
* @param int $user_id
* @param array $answer_ids Array of answer IDs to check
*
* @return array Array of existing answer_ids
*/
public function get_user_answers_bulk( $quiz_id, $user_id, $answer_ids ) {
if ( empty( $quiz_id ) || empty( $user_id ) || empty( $answer_ids ) || ! is_array( $answer_ids ) ) {
return array();
}
// Sanitize answer_ids to integers
$answer_ids = array_map( 'intval', $answer_ids );
$answer_ids = array_filter( $answer_ids );
if ( empty( $answer_ids ) ) {
return array();
}
// Build placeholders for IN clause
$placeholders = implode( ',', array_fill( 0, count( $answer_ids ), '%d' ) );
$data = array_merge( array( $quiz_id, $user_id ), $answer_ids );
$sql = 'SELECT answer_id FROM ' . tqb_table_name( 'user_answers' ) . ' WHERE quiz_id = %d AND user_id = %d AND answer_id IN (' . $placeholders . ')';
$sql = $this->prepare( $sql, $data );
$results = $this->wpdb->get_col( $sql );
return ! empty( $results ) ? array_map( 'intval', $results ) : array();
}
/**
* Get quiz user answer from DB
*
* @param $params
*
* @return array|null
*/
public function get_detailed_user_answers_( $params = array() ) {
if ( empty( $params['quiz_id'] ) || empty( $params['user_id'] ) ) {
return false;
}
$where = ' WHERE ua.quiz_id = %d AND user_id =%d';
$sql = "SELECT * FROM " . tqb_table_name( 'user_answers' ) . " ua
LEFT JOIN " . tge_table_name( 'answers' ) . " as a
ON ua.answer_id = a.id";
$data['quiz_id'] = $params['quiz_id'];
$data['user_id'] = $params['user_id'];
$sql .= $where;
$sql = $this->prepare( $sql, $data );
return $this->wpdb->get_results( $sql, ARRAY_A );
}
/**
* Get per-category (result_id) user point totals for a quiz.
*
* Returns all result_id groups with their summed points, without
* selecting a winner. Used by the hook payload to build the full
* category breakdown.
*
* @param string $user_unique User unique identifier.
* @param int $quiz_id Quiz ID.
*
* @return array Array of rows with 'user_points' and 'result_id' keys.
*/
public function get_user_category_scores( $user_unique, $quiz_id ) {
$user = $this->get_quiz_user( $user_unique, $quiz_id );
if ( empty( $user ) ) {
return array();
}
$sql = 'SELECT IFNULL(SUM( answer.points ), 0) AS user_points, answer.result_id';
$sql .= ' FROM ' . tge_table_name( 'answers' ) . ' AS answer';
$sql .= ' INNER JOIN ' . tge_table_name( 'questions' ) . ' AS question ON question.id = answer.question_id';
$sql .= ' INNER JOIN ' . tqb_table_name( 'user_answers' ) . ' AS user_answers ON answer.id = user_answers.answer_id';
$sql .= ' WHERE (answer.result_id != 0 OR answer.result_id IS NULL)';
$sql .= ' AND user_answers.quiz_id = %d AND user_answers.user_id = %d AND question.q_type != 3';
$sql .= ' GROUP BY answer.result_id';
$data = $this->wpdb->get_results( $this->prepare( $sql, array( $quiz_id, $user['id'] ) ), ARRAY_A );
return is_array( $data ) ? $data : array();
}
public function calculate_user_points( $user_unique, $quiz_id ) {
$user = $this->get_quiz_user( $user_unique, $quiz_id );
if ( empty( $user ) ) {
return false;
}
$sql = 'SELECT IFNULL(SUM( answer.points ), 0) AS user_points, answer.result_id ';
$sql .= ' FROM ' . tge_table_name( 'answers' ) . ' AS answer ';
$sql .= ' INNER JOIN ' . tge_table_name( 'questions' ) . ' AS question ON question.id = answer.question_id ';
$sql .= ' INNER JOIN ' . tqb_table_name( 'user_answers' ) . ' AS user_answers ON answer.id = user_answers.answer_id ';
$sql .= ' WHERE (answer.result_id != 0 OR answer.result_id IS NULL)
AND user_answers.quiz_id = %d
AND user_answers.user_id = %d
AND question.q_type != 3';
$sql .= ' GROUP BY answer.result_id';
$data = $this->wpdb->get_results( $this->prepare( $sql, array( $quiz_id, $user['id'] ) ), ARRAY_A );
$quiz_type = TQB_Post_meta::get_quiz_type_meta( $quiz_id );
$end_result['user_points'] = null;
$end_result['result_id'] = null;
$end_result['quiz_completed'] = ! empty( $user['completed_quiz'] );
// Build category breakdown from all result groups before picking the winner.
$end_result['category_breakdown'] = array();
if ( is_array( $data ) && ! empty( $data ) ) {
$end_result['category_breakdown'] = array_values( array_filter( $data, function ( $item ) {
return isset( $item['result_id'] ) && 0 !== (int) $item['result_id'];
} ) );
}
if ( empty( $data ) && Thrive_Quiz_Builder::QUIZ_TYPE_PERSONALITY == $quiz_type['type'] ) {
$results = $this->get_quiz_results( $quiz_id );
$end_result['result_id'] = isset( $results[0]['id'] ) ? $results[0]['id'] : null;
$end_result['user_points'] = true;
} else {
$category_breakdown = $end_result['category_breakdown'];
foreach ( $data as $result ) {
if ( empty( $end_result['user_points'] ) || $result['user_points'] > $end_result['user_points'] ) {
$end_result = $result;
}
}
$end_result['category_breakdown'] = $category_breakdown;
}
$end_result['quiz_id'] = $quiz_id;
$end_result['quiz_type'] = $quiz_type['type'];
$end_result['extra'] = '';
if ( Thrive_Quiz_Builder::QUIZ_TYPE_PERCENTAGE == $quiz_type['type'] ) {
$end_result['extra'] = '%';
$question_manager = new TGE_Question_Manager( $quiz_id );
$min_max = $question_manager->get_min_max_flow();
$end_result['max_points'] = intval( $min_max['max'] );
$end_result['min_points'] = intval( $min_max['min'] );
} else if ( Thrive_Quiz_Builder::QUIZ_TYPE_RIGHT_WRONG == $quiz_type['type'] ) {
$end_result['total_questions'] = $this->count_user_answered_questions( $user['id'], $quiz_id );
$end_result['total_valid_questions'] = 0;
$question_manager = new TGE_Question_Manager( $quiz_id );
$questions = $question_manager->get_quiz_questions();
foreach ( $questions as $question ) {
if ( ! in_array( (int) $question['q_type'], [ 1, 2 ] ) ) {
continue;
}
$user_answer_for_question = $this->get_user_answers( [
'quiz_id' => $quiz_id,
'user_id' => $user['id'],
'question_id' => $question['id'],
] );
if ( empty( $user_answer_for_question ) ) {
continue;
}
$settings = json_decode( $question['settings'], true );
if ( ! isset( $settings['allowed_answers'] ) || (int) $settings['allowed_answers'] === 1 ) {
/**
* Old way of calculating
* We check if there is at least one correct answer to the question
*/
$query = 'SELECT count(ua.id) FROM ' . tqb_table_name( 'user_answers' ) . ' AS ua
INNER JOIN ' . tge_table_name( 'answers' ) . ' a ON ua.answer_id = a.id
INNER JOIN ' . tge_table_name( 'questions' ) . ' q ON ua.question_id = q.id
WHERE ua.quiz_id = %d AND ua.user_id = %d AND q.id = %d AND a.is_right = 1';
$query = $this->prepare( $query, [ $quiz_id, $user['id'], $question['id'] ] );
if ( (int) $this->wpdb->get_var( $query ) > 0 ) {
$end_result['total_valid_questions'] ++;
}
} else {
/**
* New way of calculating
* We check if the user answers has at least one wrong answer
*/
$query = 'SELECT COUNT(a.id) FROM ' . tge_table_name( 'answers' ) . ' a INNER JOIN ' . tge_table_name( 'questions' ) . ' q1 on q1.id = a.question_id
LEFT JOIN ' . tqb_table_name( 'user_answers' ) . ' ua ON a.id = ua.answer_id AND ua.user_id = %d
WHERE a.quiz_id = %d AND a.question_id = %d AND ( ( a.is_right = 1 AND ua.id IS NULL ) OR ( a.is_right = 0 AND ua.id IS NOT NULL))';
$query = $this->prepare( $query, [ $user['id'], $quiz_id, $question['id'] ] );
if ( (int) $this->wpdb->get_var( $query ) === 0 ) {
$end_result['total_valid_questions'] ++;
}
}
}
$end_result['user_points'] = $end_result['total_valid_questions'];
}
return $end_result;
}
/**
* Get user's points from a quiz
*
* @param $user_unique
* @param $quiz_id
*
* @return array|null
*/
public function get_user_points( $user_unique, $quiz_id ) {
$quiz_type = TQB_Post_meta::get_quiz_type_meta( $quiz_id, true );
/**
* No points for Survey Quiz
*/
if ( 'survey' === $quiz_type ) {
return false;
}
$user = $this->get_quiz_user( $user_unique, $quiz_id );
if ( empty( $user ) ) {
return false;
}
return isset( $user['points'] ) ? $user['points'] : '-';
}
/**
* Update the user's points from a quiz
*
* @param $answer
* @param $user
*
* @return array|null
*/
public function update_user_points( $answer, $user ) {
$user['points'] = isset( $user['points'] ) ? $user['points'] : 0;
return $this->save_quiz_user( array( 'id' => $user['id'], 'points' => ( $user['points'] + $answer['points'] ) ) );
}
/**
* Clone variation database method
*
* @param int $id
*
* @return int
*/
public function clone_variation( $id ) {
$query = 'INSERT INTO ' . tqb_table_name( 'variations' ) . ' (quiz_id, date_added, date_modified, page_id, parent_id, post_title,tcb_fields, content)
SELECT quiz_id, NOW(), NOW(), page_id, parent_id, CONCAT("' . __( 'Copy of ', 'thrive-quiz-builder' ) . '",post_title),tcb_fields, content FROM ' . tqb_table_name( 'variations' ) . ' WHERE id = %d';
$query = $this->prepare( $query, array( 'id' => $id ) );
$this->wpdb->query( $query );
/* Store the variation ID for the case when we perform another insert unrelated to variation */
$variation_id = $this->wpdb->insert_id;
$this->replace_variation_id( $id, $variation_id );
return $variation_id;
}
/**
* Replace variation id inside content
*
* @param $initial
* @param $after
*
* @return int
*/
public function replace_variation_id( $initial, $after ) {
$variation = $this->get_variation( $after );
if ( empty( $variation ) ) {
return false;
}
/* We need to save the form settings from the clone */
if ( method_exists( FormSettings::class, 'save_form_settings_from_duplicated_content' ) ) {
$variation['content'] = FormSettings::save_form_settings_from_duplicated_content( $variation['content'], (int) $variation['page_id'] );
}
$content = str_replace( 'name="tqb-variation-variation_id" class="tqb-hidden-form-info" value="' . $initial . '"', 'name="tqb-variation-variation_id" class="tqb-hidden-form-info" value="' . $after . '"', $variation['content'] );
return $this->save_variation( array( 'id' => $after, 'content' => $content ) );
}
/**
* get data for completion report
*
* @param array $filters
*
* @return array
*/
public function get_quiz_completion_report( $quiz_id, $filters = array() ) {
$timezone_diff = current_time( 'timestamp' ) - time();
if ( empty( $filters['interval'] ) ) {
$filters['interval'] = 'day';
}
switch ( $filters['interval'] ) {
case 'month':
$date_interval = 'CONCAT(MONTHNAME(`user`.`date_started`)," ", YEAR(`user`.`date_started`)) as date_interval';
break;
case 'week':
$year = 'IF( WEEKOFYEAR(`user`.`date_started`) = 1 AND MONTH(`user`.`date_started`) = 12, 1 + YEAR(`user`.`date_started`), YEAR(`user`.`date_started`) )';
$date_interval = "CONCAT('Week ', WEEKOFYEAR(`user`.`date_started`), ', ', {$year}) as date_interval";
break;
case 'day':
$date_interval = 'DATE(`user`.`date_started`) as date_interval';
break;
}
if ( empty( $filters['location'] ) || $filters['location'] === 'all' ) {
$quiz_location = '';
} else {
$quiz_location = ' AND object_id=' . $filters['location'];
}
$sql = 'SELECT IFNULL(COUNT( user.id ), 0) AS user_count, quiz_id, ' . $date_interval;
$sql .= ' FROM ' . tqb_table_name( 'users' ) . ' AS `user` ';
$sql .= ' WHERE 1 AND completed_quiz=1 AND ignore_user IS NULL';
$params = array();
if ( empty( $filters['date'] ) ) {
$filters['date'] = Thrive_Quiz_Builder::TQB_LAST_7_DAYS;
}
$data_interval = $this->get_report_date_interval( $filters );
$sql .= $data_interval['date_interval'];
if ( ! empty( $quiz_id ) ) {
$sql .= ' AND quiz_id = %d';
$params [] = $quiz_id;
}
$sql .= $quiz_location;
$sql .= ' GROUP BY quiz_id, date_interval ORDER BY date_interval ';
$data = $this->wpdb->get_results( $this->prepare( $sql, $params ), ARRAY_A );
$dates = tqb_generate_dates_interval( $data_interval['start_date'], $data_interval['end_date'], $filters['interval'] );
$quizzes = array();
$table_quiz = array();
foreach ( $data as $i => $quiz ) {
if ( empty( $quizzes[ $quiz['quiz_id'] ] ) ) {
$quiz_post = get_post( $quiz['quiz_id'] );
if ( empty( $quiz_post ) ) {
unset( $data[ $i ] );
continue;
}
$table_quiz[ $quiz['quiz_id'] ] = intval( $quiz['user_count'] );
$quizzes[ $quiz['quiz_id'] ] = array(
'data' => array( $quiz['date_interval'] => intval( $quiz['user_count'] ) ),
'name' => $quiz_post->post_title,
'id' => $quiz_post->ID,
);
$data[ $i ]['name'] = $quiz_post->post_title;
} else {
$quizzes[ $quiz['quiz_id'] ]['data'][ $quiz['date_interval'] ] = intval( $quiz['user_count'] );
$table_quiz[ $quiz['quiz_id'] ] += intval( $quiz['user_count'] );
$data[ $i ]['name'] = $quizzes[ $quiz['quiz_id'] ]['name'];
}
}
//add zeros
foreach ( $quizzes as $key => $quiz ) {
$count_array = array();
foreach ( $dates as $k => $date ) {
$count_array[ $k ] = 0;
foreach ( $quiz['data'] as $t => $count ) {
if ( $filters['interval'] == 'day' ) {
$t = date( 'd M, Y', strtotime( $t ) );
}
if ( $date == $t ) {
$count_array[ $k ] = $count;
}
}
}
$quizzes[ $key ]['name'] = $quizzes[ $key ]['name'] . ': ' . $table_quiz[ $key ];
$quizzes[ $key ]['data'] = $count_array;
}
return array( 'graph_quiz' => $quizzes, 'intervals' => $dates, 'table_quizzes' => $data );
}
public function get_quiz_locations( $quiz_id ) {
$sql = 'SELECT GROUP_CONCAT( DISTINCT object_id ) AS locations, quiz_id FROM ' . tqb_table_name( 'users' ) . ' AS `user` ';
$sql .= ' WHERE 1 AND completed_quiz=1 AND ignore_user IS NULL';
$params = array();
if ( ! empty( $quiz_id ) ) {
$sql .= ' AND quiz_id = %d';
$params [] = $quiz_id;
}
$sql .= ' GROUP BY quiz_id ';
$data = $this->wpdb->get_results( $this->prepare( $sql, $params ), ARRAY_A );
$locations = array();
if ( ! empty( $data ) ) {
$post_ids = array_map( 'intval', explode( ",", $data[0]['locations'] ) );
foreach ( $post_ids as $post_id ) {
if ( $post_id ) {
$post = get_post( $post_id );
if ( ! empty( $post ) ) {
$details = array( 'post_id' => $post_id, 'post_title' => $post->post_title );
/**
* Gets the course name that the course overview post belongs to
*/
$details = apply_filters( 'tqb_get_course_overview_details', $details, $post );
$locations[ $post->post_type ] [] = $details;
}
}
}
}
return $locations;
}
public function get_report_date_interval( $filter ) {
$date_interval = '';
$end_date = '';
$timezone_diff = current_time( 'timestamp' ) - time();
switch ( $filter['date'] ) {
case Thrive_Quiz_Builder::TQB_LAST_7_DAYS :
$start_date = date( 'Y-m-d', ( strtotime( '-7 days' ) + $timezone_diff ) );
$date_interval = ' AND `user`.`date_started` >= "' . $start_date . '" ';
break;
case Thrive_Quiz_Builder::TQB_LAST_30_DAYS :
$start_date = date( 'Y-m-d', ( strtotime( '-30 days' ) + $timezone_diff ) );
$date_interval = ' AND `user`.`date_started` >= "' . $start_date . '" ';
break;
case Thrive_Quiz_Builder::TQB_THIS_MONTH :
$start_date = date( 'Y-m-d', ( strtotime( date( '01-m-Y' ) ) + $timezone_diff ) );
$date_interval = ' AND `user`.`date_started` >= "' . $start_date . '" ';
break;
case Thrive_Quiz_Builder::TQB_LAST_MONTH :
$start_date = date( 'Y-m-d', ( strtotime( 'first day of last month' ) + $timezone_diff ) );
$end_date = date( 'Y-m-d', ( strtotime( '01-m-Y' ) ) + $timezone_diff );
$date_interval = ' AND `user`.`date_started` >= "' . $start_date . '" AND `user`.`date_started` < "' . $end_date . '" ';
break;
case Thrive_Quiz_Builder::TQB_THIS_YEAR :
$start_date = date( 'Y-m-d', ( strtotime( date( 'Y-01-01' ) ) + $timezone_diff ) );
$date_interval = ' AND `user`.`date_started` >= "' . $start_date . '" ';
break;
case Thrive_Quiz_Builder::TQB_LAST_YEAR :
$year = date( 'Y' ) - 1;
$start_date = date( 'Y-m-d', ( mktime( 0, 0, 0, 1, 1, $year ) + $timezone_diff ) );
$end_date = date( 'Y-m-d', ( mktime( 0, 0, 0, 12, 31, $year ) + $timezone_diff ) );
$date_interval = ' AND `user`.`date_started` >= "' . $start_date . '" AND `user`.`date_started` < "' . $end_date . '" ';
break;
case Thrive_Quiz_Builder::TQB_LAST_12_MONTHS :
$start_date = date( 'Y-m-d', ( strtotime( '-1 year', time() ) + $timezone_diff ) );
$date_interval = ' AND `user`.`date_started` >= ' . $start_date . ' ';
break;
case Thrive_Quiz_Builder::TQB_CUSTOM_DATE_RANGE :
$start_date = $filter['start_date'];
$end_date = date( 'Y-m-d H:i:s', ( strtotime( '+1 day', ( strtotime( $filter['end_date'] ) - 1 ) ) + $timezone_diff ) );
$date_interval = ' AND `user`.`date_started` >= "' . $start_date . '" AND `user`.`date_started` < "' . $end_date . '" ';
break;
}
return array(
'date_interval' => $date_interval,
'start_date' => $start_date,
'end_date' => empty( $end_date ) ? date( 'Y-m-d', ( time() + $timezone_diff ) ) : $end_date,
);
}
/**
* Get quiz data for questions report
*
* @param $quiz_id
* @param array $params
*
* @return false|array
*/
public function get_questions_report_data( $quiz_id, $params ) {
$sql = 'SELECT
IFNULL(COUNT( user_answer.id ), 0) AS answer_count,
answer.question_id,
answer.id AS answer_id,
answer.text AS answer_text,
answer.image AS answer_image,
question.text AS question_text,
question.views AS question_views,
question.q_type AS question_type
';
$sql .= ' FROM ' . tge_table_name( 'answers' ) . ' AS answer ';
$sql .= ' LEFT JOIN ' . tqb_table_name( 'user_answers' ) . ' AS user_answer ON answer.id = user_answer.answer_id ';
$sql .= ' INNER JOIN ' . tqb_table_name( 'users' ) . ' AS user ON user.id = user_answer.user_id ';
$sql .= ' LEFT JOIN ' . tge_table_name( 'questions' ) . ' AS question ON question.id = answer.question_id ';
$sql .= ' WHERE answer.quiz_id = ' . $quiz_id . ' AND user.ignore_user IS NULL';// ' AND user.completed_quiz = 1';
if ( ! empty( $params['location'] ) ) {
$sql .= ' AND user.object_id = ' . esc_sql( $params['location'] );
}
$sql .= ' GROUP BY answer.question_id, answer.id ';
$answers_sql = 'SELECT * FROM ' . tqb_table_name( 'user_answers' ) . ' WHERE question_id = %d AND answer_id = %s';
$data = $this->wpdb->get_results( $this->prepare( $sql, array( 'quiz_id' => $quiz_id ) ), ARRAY_A );
$questions = array();
$colors = tqb()->chart_colors();
foreach ( $data as $entry ) {
$image = json_decode( (string) $entry['answer_image'] );
if ( empty( $image ) ) {
$image = array( 'url' => $entry['answer_image'] );
}
if ( empty( $questions[ $entry['question_id'] ] ) ) {
$structure_manager = new TQB_Structure_Manager( $quiz_id );
$structure = $structure_manager->get_quiz_structure_meta();
$questions[ $entry['question_id'] ] = array(
'text' => $entry['question_text'],
'answers' => array(
$entry['answer_id'] => array(
'text' => $entry['answer_text'],
'count' => $entry['answer_count'],
'image' => $image,
),
),
'total' => $entry['answer_count'],
'views' => $entry['question_views'],
'id' => $entry['question_id'],
'views_counted' => isset( $structure['count_views'] ) ? $structure['count_views'] : false,
);
} else {
$questions[ $entry['question_id'] ]['answers'][ $entry['answer_id'] ] = array(
'text' => $entry['answer_text'],
'count' => $entry['answer_count'],
'image' => $image,
);
$questions[ $entry['question_id'] ]['total'] += $entry['answer_count'];
}
$questions[ $entry['question_id'] ]['question_type'] = $entry['question_type'];
if ( intval( $entry['question_type'] ) === 3 ) {
$users_answers = $this->wpdb->get_results(
$this->prepare(
$answers_sql,
array(
$entry['question_id'],
$entry['answer_id'],
)
),
ARRAY_A
);
foreach ( $users_answers as &$temp_answer ) {
$temp_answer['answer_text'] = nl2br( sanitize_textarea_field( stripslashes( $temp_answer['answer_text'] ) ) );
}
$questions[ $entry['question_id'] ]['user_answers'] = $users_answers ? $users_answers : array();
}
}
foreach ( $questions as $key => $question ) {
$index = 0;
foreach ( $question['answers'] as $id => $answer ) {
if ( $question['total'] ) {
$questions[ $key ]['answers'][ $id ]['percent'] = round( $answer['count'] * 100 / $question['total'], 2 );
$questions[ $key ]['answers'][ $id ]['color'] = $colors[ $index % count( $colors ) ];
$index ++;
}
}
}
return $questions;
}
/**
* @param $quiz_id
* @param array $filters
*
* @return array
*/
public function get_full_questions_report_data( $quiz_id, $filters = array() ) {
// The query
$sql = 'SELECT
IFNULL(COUNT( user_answer.id ), 0) AS answer_count,
answer.question_id,
answer.id AS answer_id,
answer.text AS answer_text,
answer.image AS answer_image,
question.id AS q_id,
question.text AS question_text,
question.views AS question_views,
question.q_type AS question_type
';
// Filter columns in sql
if ( ! empty( $filters['columns'] ) && is_array( $filters['columns'] ) ) {
$columns = implode( ', ', array_map(
function ( $v, $k ) {
return sprintf( "%s AS %s", esc_sql( $k ), esc_sql( $v ) );
},
$filters['columns'],
array_keys( $filters['columns'] )
) );
$sql = "SELECT {$columns}";
}
$sql .= ' FROM ' . tge_table_name( 'answers' ) . ' AS answer ';
$sql .= ' LEFT JOIN ' . tqb_table_name( 'user_answers' ) . ' AS user_answer ON answer.id = user_answer.answer_id ';
$sql .= ' INNER JOIN ' . tqb_table_name( 'users' ) . ' AS `user` ON user.id = user_answer.user_id ';
$sql .= ' LEFT JOIN ' . tge_table_name( 'questions' ) . ' AS question ON question.id = answer.question_id ';
$sql .= ' WHERE answer.quiz_id = ' . $quiz_id . ' AND user.ignore_user IS NULL';
// Filter GROUP BY in sql
if ( ! empty( $filters['group_by'] ) && is_array( $filters['group_by'] ) ) {
$group_by = implode( ', ', array_map(
function ( $v ) {
return sprintf( "%s", esc_sql( $v ) );
},
$filters['group_by']
) );
$sql .= " GROUP BY {$group_by}";
} else {
$sql .= ' GROUP BY answer.question_id, answer.id ';
}
$answers_sql = 'SELECT * FROM ' . tqb_table_name( 'user_answers' ) . ' WHERE question_id = %d AND answer_id = %s';
$data = $this->wpdb->get_results( $this->prepare( $sql, array( 'quiz_id' => $quiz_id ) ), ARRAY_A );
// Build questions and answers array as required
$questions = array();
foreach ( $data as $entry ) {
switch ( (int) $entry['question_type'] ) {
// Multiple with image
case 2:
if ( ! empty( $entry['answer_image'] ) ) {
$image_obj = json_decode( $entry['answer_image'] );
if ( ! empty( $image_obj->sizes ) && ! empty( $image_obj->sizes->thumbnail ) && ! empty( $image_obj->sizes->thumbnail->url ) ) {
$img_arr = ( explode( '/', $image_obj->sizes->thumbnail->url ) );
$img_name = end( $img_arr );
$questions[ $entry['question_text'] . "__{$entry['q_id']}" ][ $entry['uid'] ] = ! empty( $img_name ) ? $img_name : 'Image X';
}
}
break;
// Open ended question
case 3:
$oe_data = array();
$users_answers = $this->wpdb->get_results(
$this->prepare(
$answers_sql,
array(
$entry['question_id'],
$entry['answer_id'],
)
),
ARRAY_A
);
foreach ( $users_answers as $temp_answer ) {
$oe_data[ $temp_answer['user_id'] ] = ! empty( $temp_answer['answer_text'] ) ? nl2br( sanitize_textarea_field( $temp_answer['answer_text'] ) ) : '';
}
$questions[ $entry['question_text'] . "__{$entry['q_id']}" ] = $oe_data;
break;
default:
$questions[ $entry['question_text'] . "__{$entry['q_id']}" ][ $entry['uid'] ] = ! empty( $entry['answer_text'] ) ? $entry['answer_text'] : '';
break;
}
}
if ( empty( $questions ) ) {
return array();
}
// Add all user id's that have answered
$all_uids = array();
foreach ( $questions as $key => $question ) {
foreach ( $question as $u_id => $answer ) {
if ( ! in_array( $u_id, $all_uids ) ) {
$all_uids[] = $u_id;
}
}
}
sort( $all_uids );
/**
* Loop trough all users that answered and make sure that every question has all user id's for positioning in the csv file
*/
$headers = array();
foreach ( $all_uids as $user_id ) {
foreach ( $questions as $q_key => $answer ) {
// Build headers
$headers[ $q_key ] = substr( $q_key, 0, strpos( $q_key, "__" ) ); // for questions with the same name
// Build body
if ( ! isset( $questions[ $q_key ][ $user_id ] ) ) {
$questions[ $q_key ][ $user_id ] = '';
}
ksort( $questions[ $q_key ] );
}
}
$return = array(
'headers' => array_values( $headers ),
'body' => array_values( array_map( 'array_values', $questions ) ),
);
return $return;
}
/**
* @param array $args
*
* @return array|object|null
*/
public function get_user_answers_with_questions( $args = array() ) {
$user_answers = tqb_table_name( 'user_answers' );
$questions = tge_table_name( 'questions' );
$answers = tge_table_name( 'answers' );
$user_id = ! empty( $args['user_id'] ) ? $args['user_id'] : 0;
$quiz_id = ! empty( $args['quiz_id'] ) ? $args['quiz_id'] : 0;
$sql = "SELECT ua.id, ua.question_id, ua.answer_text, q.text as q_text, q.q_type, a.text as a_text FROM {$user_answers} as ua ";
$sql .= "LEFT JOIN {$questions} as q ON ua.question_id = q.id ";
$sql .= "LEFT JOIN {$answers} as a ON a.question_id = ua.question_id ";
$sql .= 'WHERE ua.user_id = %d AND ua.quiz_id = %d AND ua.answer_id = a.id ';
$sql .= 'ORDER BY ua.id ';
$data = $this->wpdb->get_results(
$this->prepare(
$sql,
array(
'user_id' => $user_id,
'quiz_id' => $quiz_id,
)
),
ARRAY_A
);
return $data;
}
/**
* Get a rough map of the quiz's questions and answers with the next question ids
* This array is oriented towards displaying the user's answers in the email sent at the end
*
* @param int $quiz_id
*
* @return array|object|null
*/
public function get_quiz_map( $quiz_id ) {
// Cache key for this quiz's map
$cache_key = 'tqb_quiz_map_' . $quiz_id;
// Try to get from cache
$cached = get_transient( $cache_key );
if ( false !== $cached ) {
return $cached;
}
$params[] = $quiz_id;
$questions = tge_table_name( 'questions' );
$answers = tge_table_name( 'answers' );
$sql = 'SELECT q.id as question_id, a.id as answer_id, q.start, q.next_question_id as q_next_id, a.next_question_id as a_next_id';
$sql .= ", a.text as a_text, q.text as q_text, q.q_type as q_type FROM {$questions} AS q ";
$sql .= " JOIN {$answers} as a on q.id = a.question_id ";
$sql .= 'WHERE q.quiz_id = %d ';
$sql .= 'ORDER BY q.start DESC ';
$result = $this->wpdb->get_results( $this->prepare( $sql, $params ), ARRAY_A );
// Cache for 1 hour (3600 seconds)
set_transient( $cache_key, $result, HOUR_IN_SECONDS );
return $result;
}
/**
* Invalidate quiz map cache for a specific quiz
*
* @param int $quiz_id
*
* @return bool
*/
public function invalidate_quiz_map_cache( $quiz_id ) {
$cache_key = 'tqb_quiz_map_' . $quiz_id;
return delete_transient( $cache_key );
}
/**
* Get category breakdown with names for a quiz
*
* Enriches category_breakdown data with category names from the results table.
* Returns ALL categories for the quiz, with 0 points for unscored ones.
*
* @param array $category_breakdown Raw category breakdown from user quiz data.
* @param int $quiz_id Quiz ID.
*
* @return array Enriched category data with id, name, and points.
*/
public function get_category_breakdown_with_names( $category_breakdown, $quiz_id ) {
if ( empty( $quiz_id ) || ! isset( $this->wpdb ) || ! is_object( $this->wpdb ) ) {
return array();
}
$quiz_id = (int) $quiz_id;
// Get ALL categories for this quiz
$all_categories = $this->get_quiz_results( $quiz_id );
if ( empty( $all_categories ) ) {
return array();
}
// Index user scores by result_id for quick lookup
$scores_by_id = array();
if ( ! is_array( $category_breakdown ) ) {
$category_breakdown = array();
}
foreach ( $category_breakdown as $category ) {
if ( ! is_array( $category ) || ! isset( $category['result_id'] ) ) {
continue;
}
$scores_by_id[ (int) $category['result_id'] ] = isset( $category['user_points'] ) ? (int) $category['user_points'] : 0;
}
// Build enriched array with ALL categories (0 points for unscored)
$result = array();
foreach ( $all_categories as $category ) {
if ( ! is_array( $category ) || ! isset( $category['id'] ) ) {
continue;
}
$cat_id = (int) $category['id'];
$result[] = array(
'id' => $cat_id,
'name' => isset( $category['text'] ) ? $category['text'] : '',
'points' => isset( $scores_by_id[ $cat_id ] ) ? $scores_by_id[ $cat_id ] : 0,
);
}
return $result;
}
/**
* Calculate share and percent values for categories
*
* @param array $categories Array of categories with 'points' key.
* @param array $max_possible Optional. Array of max possible points keyed by category ID.
*
* @return array Categories with added 'share' and optionally 'percent' keys.
*/
public function calculate_category_shares( $categories, $max_possible = array() ) {
if ( empty( $categories ) || ! is_array( $categories ) ) {
return array();
}
// Validate array structure before processing
$result = array();
$total_points = 0;
// First pass: validate and calculate total points
foreach ( $categories as $category ) {
if ( ! is_array( $category ) || ! isset( $category['points'] ) ) {
continue;
}
$total_points += (int) $category['points'];
}
// Second pass: add share and percent calculations
foreach ( $categories as $category ) {
if ( ! is_array( $category ) || ! isset( $category['points'] ) ) {
continue;
}
$points = (int) $category['points'];
// Calculate share percentage
$category['share'] = 0;
if ( $total_points > 0 ) {
$share = ( $points / $total_points ) * 100;
// Format: whole numbers >= 1%, 1 decimal < 1%
$category['share'] = $share >= 1 ? round( $share ) : round( $share, 1 );
}
// Calculate percent against maximum possible if provided
if ( empty( $max_possible ) || ! is_array( $max_possible ) ) {
$result[] = $category;
continue;
}
$cat_id = isset( $category['id'] ) ? (int) $category['id'] : 0;
$max = isset( $max_possible[ $cat_id ] ) ? (int) $max_possible[ $cat_id ] : 0;
$category['percent'] = 0;
if ( $max > 0 ) {
$percent = ( $points / $max ) * 100;
$category['percent'] = $percent >= 1 ? round( $percent ) : round( $percent, 1 );
}
$result[] = $category;
}
return $result;
}
/**
* Get maximum possible points per category for a quiz
*
* For each question, finds the highest-scoring answer per category,
* then sums those maximums across all questions.
*
* @param int $quiz_id Quiz ID.
*
* @return array Associative array of result_id => max_possible_points.
*/
public function get_category_max_possible_points( $quiz_id ) {
if ( empty( $quiz_id ) || ! isset( $this->wpdb ) || ! is_object( $this->wpdb ) ) {
return array();
}
$quiz_id = (int) $quiz_id;
// Get all answers with their category and points
$sql = 'SELECT
a.question_id,
a.result_id,
a.points
FROM ' . tge_table_name( 'answers' ) . ' a
INNER JOIN ' . tge_table_name( 'questions' ) . ' q ON a.question_id = q.id
WHERE q.quiz_id = %d';
$answers = $this->wpdb->get_results( $this->prepare( $sql, array( $quiz_id ) ), ARRAY_A );
if ( empty( $answers ) || ! is_array( $answers ) ) {
return array();
}
// For each question, find max points per category
$question_max = array();
foreach ( $answers as $answer ) {
// Validate required array keys exist
if ( ! isset( $answer['question_id'], $answer['result_id'], $answer['points'] ) ) {
continue;
}
$q_id = (int) $answer['question_id'];
$r_id = (int) $answer['result_id'];
$pts = (int) $answer['points'];
if ( ! isset( $question_max[ $q_id ] ) ) {
$question_max[ $q_id ] = array();
}
if ( ! isset( $question_max[ $q_id ][ $r_id ] ) || $pts > $question_max[ $q_id ][ $r_id ] ) {
$question_max[ $q_id ][ $r_id ] = $pts;
}
}
// Sum max points per category across all questions
$max_possible = array();
foreach ( $question_max as $q_id => $categories ) {
foreach ( $categories as $r_id => $max_pts ) {
if ( ! isset( $max_possible[ $r_id ] ) ) {
$max_possible[ $r_id ] = 0;
}
$max_possible[ $r_id ] += $max_pts;
}
}
return $max_possible;
}
/**
* @param $user_id
*
* @return array|null
*/
public function get_last_user_answer( $user_id ) {
$user_answers = tqb_table_name( 'user_answers' );
$answers = tge_table_name( 'answers' );
$sql = "SELECT * from {$answers} WHERE id = (SELECT answer_id from {$user_answers} WHERE user_id = %d ORDER BY id DESC LIMIT 1)";
return $this->wpdb->get_row( $this->prepare( $sql, array( 'user_id' => $user_id ) ), ARRAY_A );
}
/**
* Check if we have a design variation containing the specific string
*
* @param $string
*
* @return boolean
*/
public function search_string_in_designs( $string ) {
$sql = 'SELECT `id` FROM ' . tqb_table_name( 'variations' ) . ' WHERE content LIKE %s';
$this->wpdb->query( $this->prepare( $sql, [ "%$string%" ] ) );
return $this->wpdb->num_rows > 0;
}
/**
* @param array $filters
*
* @return array|null
*/
public function get_log_by_filters( $filters = array() ) {
$params = array();
$where = '';
if ( ! empty( $filters['user_unique'] ) ) {
$params[] = $filters['user_unique'];
$where .= ' AND user_unique=%s';
}
if ( ! empty( $filters['event_type'] ) ) {
if ( is_array( $filters['event_type'] ) ) {
$where .= ' AND event_type IN (' . implode( ',', array_map( 'absint', $filters['event_type'] ) ) . ')';
} else {
$params[] = $filters['event_type'];
$where .= ' AND event_type=%d';
}
}
if ( ! empty( $filters['page_id'] ) ) {
$params[] = $filters['page_id'];
$where .= ' AND page_id=%d';
}
if ( ! empty( $filters['limit'] ) ) {
$params[] = $filters['limit'];
$where .= ' LIMIT %d';
}
$sql = $this->prepare( 'SELECT * FROM ' . tqb_table_name( 'event_log' ) . " WHERE 1 {$where}", $params );
return ! empty( $filters['limit'] ) && $filters['limit'] === 1 ? $this->wpdb->get_row( $sql, ARRAY_A ) : $this->wpdb->get_results( $sql, ARRAY_A );
}
/**
* Returns the total number of answered questions from the database
* This query counts DISTINCT questions in order to support the multiple answer feature
* It is used to display the result for a R/W quiz
* Example 0/3
*
* @param integer $user_id
* @param integer $quiz_id
*
* @return int
*/
public function count_user_answered_questions( $user_id, $quiz_id ) {
$table_user_answers = tqb_table_name( 'user_answers' );
$table_answers = tge_table_name( 'answers' );
$table_questions = tge_table_name( 'questions' );
$sql = $this->wpdb->prepare( "SELECT COUNT(DISTINCT ua.question_id) FROM {$table_user_answers} AS ua INNER JOIN {$table_answers} AS a on ua.answer_id = a.id INNER JOIN {$table_questions} as q ON ua.question_id = q.id WHERE ua.quiz_id = %d AND ua.user_id = %d AND q.q_type != 3", [ $quiz_id, $user_id ] );
return (int) $this->wpdb->get_var( $sql );
}
/**
* Get user stats (users_started and completed_count) for multiple quizzes in a single query
*
* @param array $quiz_ids Array of quiz IDs
*
* @return array Associative array keyed by quiz_id: [ quiz_id => ['users_started' => X, 'completed_count' => Y] ]
*/
public function get_batch_quiz_user_stats( $quiz_ids ) {
if ( empty( $quiz_ids ) ) {
return array();
}
$placeholders = implode( ',', array_fill( 0, count( $quiz_ids ), '%d' ) );
$sql = $this->wpdb->prepare(
'SELECT quiz_id, COUNT(*) as users_started, SUM(CASE WHEN completed_quiz = 1 THEN 1 ELSE 0 END) as completed_count FROM ' . tqb_table_name( 'users' ) . " WHERE quiz_id IN ($placeholders) AND ignore_user IS NULL GROUP BY quiz_id",
$quiz_ids
);
$results = $this->wpdb->get_results( $sql );
$stats = array();
foreach ( $results as $row ) {
$stats[ $row->quiz_id ] = array(
'users_started' => (int) $row->users_started,
'completed_count' => (int) $row->completed_count,
);
}
return $stats;
}
/**
* Get social share counts for multiple page IDs in a single query
*
* @param array $page_ids Array of page IDs
*
* @return array Associative array keyed by page_id: [ page_id => count ]
*/
public function get_batch_social_shares_counts( $page_ids ) {
if ( empty( $page_ids ) ) {
return array();
}
$placeholders = implode( ',', array_fill( 0, count( $page_ids ), '%d' ) );
$sql = $this->wpdb->prepare(
'SELECT page_id, COUNT(*) as count FROM ' . tqb_table_name( 'event_log' ) . " WHERE page_id IN ($placeholders) AND social_share = 1 GROUP BY page_id",
$page_ids
);
$results = $this->wpdb->get_results( $sql );
$counts = array();
foreach ( $results as $row ) {
$counts[ $row->page_id ] = (int) $row->count;
}
return $counts;
}
/**
* Get subscriber counts for multiple page IDs in a single query
*
* @param array $page_ids Array of page IDs
*
* @return array Associative array keyed by page_id: [ page_id => count ]
*/
public function get_batch_page_subscribers( $page_ids ) {
if ( empty( $page_ids ) ) {
return array();
}
$placeholders = implode( ',', array_fill( 0, count( $page_ids ), '%d' ) );
$sql = $this->wpdb->prepare(
'SELECT page_id, COUNT(*) as count FROM ' . tqb_table_name( 'event_log' ) . " WHERE page_id IN ($placeholders) AND event_type = 2 AND optin = 1 GROUP BY page_id",
$page_ids
);
$results = $this->wpdb->get_results( $sql );
$counts = array();
foreach ( $results as $row ) {
$counts[ $row->page_id ] = (int) $row->count;
}
return $counts;
}
}
$tqbdb = new TQB_Database();