';
} else {
echo '
';
}
echo 'Premium ' . esc_html( $text_label ) . '';
$attrs = array(
'type' => 'text',
'name' => $text_name,
'value' => $text_value,
);
if ( ! $can_use ) {
$attrs['disabled'] = 'disabled';
}
$this->open_tag( 'input', $attrs );
echo '
';
}
}
}
/**
* Print plugin notices
*/
protected function print_plugin_notices() {
// Do nothing.
}
/**
* Open tag
*
* @param string $element_name HTML element name.
* @param array $attributes Attributes.
* @param bool $is_return Get or echo.
*/
protected function open_tag( $element_name, $attributes, $is_return = false ) {
$out = '<' . esc_attr( $element_name );
foreach ( $attributes as $attr => $val ) {
$out .= ' ' . esc_attr( $attr ) . '="' . esc_attr( $val ) . '"';
}
$out .= '>';
if ( $is_return ) {
return $out;
}
echo $out; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
/**
* Print admin_options_page
*/
public function print_admin_options_page() {
$tabs = $this->get_admin_tabs();
$pagenum = filter_input( INPUT_GET, 'pagenum' );
$active_tab = filter_input( INPUT_GET, 'tab' );
if ( ! $pagenum ) {
$pagenum = 1;
}
if ( ! $active_tab || ! isset( $tabs[ $active_tab ] ) ) {
$active_tab = key( $tabs );
}
echo '
' . esc_html__( 'Aurora Heatmap', 'aurora-heatmap' ) . '
';
// Print plugin notices.
$this->print_plugin_notices();
if ( isset( $_SERVER['REQUEST_METHOD'] ) &&
'POST' === $_SERVER['REQUEST_METHOD'] &&
filter_input( INPUT_POST, 'mode' ) &&
check_admin_referer( 'aurora_heatmap_action', 'aurora_heatmap_nonce' ) &&
$this->can_settings()
) {
switch ( filter_input( INPUT_POST, 'mode' ) ) {
case 'save':
$this->save_options();
add_settings_error( $this::SLUG, '', __( 'Updated options.', 'aurora-heatmap' ), 'updated' );
break;
case 'delete':
$this->delete_all();
add_settings_error( $this::SLUG, '', __( 'Deleted heatmap data.', 'aurora-heatmap' ), 'updated' );
break;
}
}
settings_errors( $this::SLUG );
echo '
';
foreach ( $tabs as $key => $value ) {
if ( ! $value ) {
continue;
}
if ( $value['can_use'] ) {
$this->open_tag(
'a',
array(
'href' => '?page=' . esc_attr( $this::SLUG ) . '&tab=' . esc_attr( $key ),
'class' => 'nav-tab' . ( $key === $active_tab ? ' nav-tab-active' : '' ),
)
);
} else {
echo '';
}
echo ' ' . esc_html( $value['name'] ) . '';
}
echo '
';
$this->print_tab_content( $active_tab );
echo '
';
}
/**
* Print tab content
*
* @param string $active_tab Active tab.
*/
protected function print_tab_content( $active_tab ) {
if ( 'view' === $active_tab && $this->can_view() ) {
include_once __DIR__ . '/class-aurora-heatmap-list.php';
$table = new Aurora_Heatmap_List( $this );
$page = filter_input( INPUT_GET, 'page' );
if ( ! $page ) {
$page = filter_input( INPUT_POST, 'page' );
}
echo '
';
?>
';
echo '
';
echo '
';
$table->prepare_items();
$table->search_box( __( 'Search page', 'aurora-heatmap' ), 'search_page' );
$table->display();
echo '';
} elseif ( 'help' === $active_tab && $this->can_settings() ) {
$this->print_help();
} elseif ( 'settings' === $active_tab && $this->can_settings() ) {
$this->print_settings();
}
}
/**
* Print help
*/
protected function print_help() {
$vs = function ( $feature, $now, ...$args ) {
echo '
| ' . esc_html( $feature ) . ' | ';
foreach ( $args as $index => $arg ) {
$col = $index === $now ? 'current' : '';
$icon = $arg ? 'dashicons-yes-alt' : 'dashicons-no-alt';
echo ' | ';
}
echo '
|---|
';
};
if ( 'basic' === $this::PLAN ) {
echo '
';
_e( 'Aurora Heatmap has a premium version with extended features.', 'aurora-heatmap' ); // phpcs:ignore WordPress.Security.EscapeOutput.UnsafePrintingFunction
echo '
';
esc_html_e( 'If you like this plugin, please consider upgrading!', 'aurora-heatmap' );
echo '
';
}
?>
|
|
|
|
|
0,
'free' => 1,
'standard' => 2,
);
$now = $plans[ $this::PLAN ];
$vs( __( 'Priority Email Support', 'aurora-heatmap' ), $now, 0, 0, 1 );
$vs( __( 'Click Heatmap', 'aurora-heatmap' ), $now, 1, 1, 1 );
$vs( __( 'Breakaway Heatmap', 'aurora-heatmap' ), $now, 0, 0, 1 );
$vs( __( 'Attention Heatmap', 'aurora-heatmap' ), $now, 0, 0, 1 );
$vs( __( 'Unread Detection', 'aurora-heatmap' ), $now, 0, 0, 1 );
$vs( __( 'URL Optimization', 'aurora-heatmap' ), $now, 1, 1, 1 );
$vs( __( 'Advanced URL Optimization', 'aurora-heatmap' ), $now, 0, 0, 1 );
$vs( __( 'Extended Retension Period (6 months)', 'aurora-heatmap' ), $now, 0, 0, 1 );
$vs( __( 'Update to the Latest Version', 'aurora-heatmap' ), $now, 1, 0, 1 );
echo ' | ';
foreach ( $plans as $i ) {
echo ( $i === $now ) ? 'Now | ' : ' | ';
}
echo '
|---|
';
_e( '* The free plan is a premium version with no license or expired.', 'aurora-heatmap' ); // phpcs:ignore WordPress.Security.EscapeOutput.UnsafePrintingFunction
echo '
';
$this->print_additional_help();
}
/**
* Print additional help
*/
protected function print_additional_help() {
// phpcs:disable WordPress.Security.EscapeOutput.UnsafePrintingFunction
?>
- Get the installer of the premium version plugin.', 'aurora-heatmap' ); ?>
- Stop the free version plugin.', 'aurora-heatmap' ); ?>
- Install / activate the installer.', 'aurora-heatmap' ); ?>
- User registration / license agreement / update plugin.', 'aurora-heatmap' ); ?>
- Delete the free version plugin.', 'aurora-heatmap' ); ?>
public_query_vars;
sort( $wp_public_query_vars );
$options_period = array(
1 => __( '1 month', 'aurora-heatmap' ),
3 => __( '3 months', 'aurora-heatmap' ),
6 => __( '6 months', 'aurora-heatmap' ),
);
$options_accuracy = array(
2 => __( 'High accuracy', 'aurora-heatmap' ),
1 => __( 'Standard', 'aurora-heatmap' ),
);
$options_report_non_singular = array(
1 => __( 'Report', 'aurora-heatmap' ),
0 => __( 'Do not report', 'aurora-heatmap' ),
);
$options_drawing_points = array(
0 => __( 'Unlimited', 'aurora-heatmap' ),
10000 => __( '10000', 'aurora-heatmap' ),
3000 => __( '3000', 'aurora-heatmap' ),
1000 => __( '1000', 'aurora-heatmap' ),
);
$options_count_bar = array(
1 => __( 'Show', 'aurora-heatmap' ),
0 => __( 'Hide', 'aurora-heatmap' ),
);
$options_keep_url_hash = array(
false => __( 'Integrated display', 'aurora-heatmap' ),
true => __( 'Individual display', 'aurora-heatmap' ),
);
$options_keep_url_query = array(
false => array(
__( 'Integrated display', 'aurora-heatmap' ),
'url_query_include',
__( 'Except for the following comma-separated parameters:', 'aurora-heatmap' ),
implode( ', ', $this->options['url_query_include'] ),
),
true => array(
__( 'Individual display', 'aurora-heatmap' ),
'url_query_exclude',
__( 'Except for the following comma-separated parameters:', 'aurora-heatmap' ),
implode( ', ', $this->options['url_query_exclude'] ),
),
);
$options_content_end_marker = array(
1 => __( 'Output', 'aurora-heatmap' ),
0 => __( 'Do not output', 'aurora-heatmap' ),
);
$options_weekly_email_sending = array(
1 => __( 'Send', 'aurora-heatmap' ),
0 => __( 'Do not send', 'aurora-heatmap' ),
);
$options_weekly_email_content_type = array(
'plain' => __( 'Plain text mail', 'aurora-heatmap' ),
'html' => __( 'HTML mail', 'aurora-heatmap' ),
);
$is_premium = 'basic' !== $this::PLAN && 'free' !== $this::PLAN;
$disabled_in_free = $is_premium ? '' : 'disabled';
?>
prefix}ahm_events WHERE page_id IN ( SELECT id FROM {$wpdb->prefix}ahm_pages WHERE title LIKE %s OR url LIKE %s ) ) ";
}
$sql = "SELECT
s.page_id2 AS id,
p.url2 AS url,
p.title,
s.click_pc,
s.breakaway_pc,
s.attention_pc,
s.click_mobile,
s.breakaway_mobile,
s.attention_mobile
FROM (SELECT
page_id2,
COUNT( event = 16 OR NULL ) AS click_pc,
COUNT( event = 32 OR NULL ) AS breakaway_pc,
COUNT( event = 48 OR NULL ) AS attention_pc,
COUNT( event = 17 OR NULL ) AS click_mobile,
COUNT( event = 33 OR NULL ) AS breakaway_mobile,
COUNT( event = 49 OR NULL ) AS attention_mobile
FROM {$wpdb->prefix}ahm_events
{$where}
GROUP BY page_id2
) as s
LEFT JOIN {$wpdb->prefix}ahm_pages AS p ON p.id = s.page_id2
WHERE (click_pc OR breakaway_pc OR attention_pc OR click_mobile OR breakaway_mobile OR attention_mobile)
ORDER BY {$param['orderby']} {$param['order']}, p.url ASC
LIMIT %d OFFSET %d";
$args = array( $sql );
if ( $param['search'] ) {
$args[] = $param['search_title'];
$args[] = $param['search_url'];
}
$args[] = $this::LIST_PER_PAGE;
$args[] = $this::LIST_PER_PAGE * ( max( $param['pagenum'], 1 ) - 1 );
$rows = $wpdb->get_results( $wpdb->prepare( ...$args ) ); // phpcs:ignore WordPress.DB
if ( ! $rows ) {
return array();
}
return $rows;
}
/**
* Get list total items for Aurora_Heatmap_List
*
* @param array $param parameters. keys are event, search, pagenum.
* @return int
*/
public function get_list_total_items( $param ) {
global $wpdb;
if ( ! $param['search'] ) {
$query = "SELECT COUNT( DISTINCT page_id2 ) FROM {$wpdb->prefix}ahm_events";
} else {
$query = $wpdb->prepare(
"SELECT COUNT( DISTINCT page_id2 ) FROM {$wpdb->prefix}ahm_events AS e INNER JOIN ( SELECT id FROM {$wpdb->prefix}ahm_pages WHERE title LIKE %s OR url LIKE %s ) AS p ON ( e.page_id2 = p.id )",
$param['search_title'],
$param['search_url']
);
}
return (int) $wpdb->get_var( $query ); // phpcs:ignore WordPress.DB
}
/**
* Filter Hook for wp_is_mobile
*
* @param bool $is_mobile Whether the request is from a mobile device or not.
* @return bool
*/
public function wp_is_mobile( $is_mobile ) {
$ahm = filter_input( INPUT_GET, 'aurora-heatmap' );
if ( $ahm && preg_match( '/^\\w+_mobile-\\d+$/', $ahm ) ) {
return true;
}
return $is_mobile;
}
/**
* Save heatmap from Ajax
*/
public function ajax_aurora_heatmap() {
global $wpdb;
header( 'Content-Type: application/json; charset=UTF-8' );
if ( ! $this->can_report() ) {
echo wp_json_encode( array( 'error' => 'cannot report' ) );
die();
}
$dataset = filter_input( INPUT_POST, 'data', FILTER_DEFAULT, FILTER_REQUIRE_ARRAY );
if ( ! $dataset ) {
echo wp_json_encode( array( 'error' => 'no data' ) );
die();
}
$events = $this::EVENT_NAMES;
$to_int = function ( $str ) {
return (int) filter_var( $str, FILTER_VALIDATE_INT | FILTER_VALIDATE_FLOAT );
};
$default = array(
'x' => 0,
'y' => 0,
'width' => 0,
'height' => 0,
);
$tz = static::wp_timezone();
// Drop unnecessary data in advance.
$dataset_checked = array();
foreach ( $dataset as $data ) {
if ( ! isset( $data['event'] ) || ! isset( $events[ $data['event'] ] ) ) {
continue;
}
$data = array_merge( $default, $data );
// Skip negative coordinates.
$data['x'] = $to_int( $data['x'] );
$data['y'] = $to_int( $data['y'] );
if ( $data['x'] < 0 || $data['y'] < 0 ) {
continue;
}
$event_id = $events[ $data['event'] ];
$data['width'] = $to_int( $data['width'] );
$data['height'] = $to_int( $data['height'] );
// Skip if out of range.
if ( ! $this->is_within_accuracy( $data['width'], $event_id ) ) {
continue;
}
$data['insert_at'] = ( new DateTime( sprintf( '%d second', $to_int( $data['time'] ) ), $tz ) )->format( 'Y-m-d H:i:s' );
$dataset_checked[] = $data;
}
if ( empty( $dataset_checked ) ) {
echo wp_json_encode( array( 'count' => 0 ), JSON_NUMERIC_CHECK );
die();
}
$url_filter = $this->make_url_filter( false );
$url = filter_input( INPUT_POST, 'url', FILTER_VALIDATE_URL );
if ( $url ) {
$url = $url_filter( $url );
}
if ( ! $url ) {
echo wp_json_encode( array( 'error' => 'no url' ) );
die();
}
$title = filter_input( INPUT_POST, 'title', FILTER_SANITIZE_SPECIAL_CHARS );
$row = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}ahm_pages WHERE url = %s", $url ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery
if ( ! $row ) {
$url2_filter = $this->make_url_filter( true );
$url2 = $url2_filter( $url );
$now = ( new DateTime( '', $tz ) )->format( 'Y-m-d H:i:s' );
$data = array(
'url' => $url,
'url2' => $url2,
'title' => $title,
'insert_at' => $now,
'update_at' => $now,
);
$wpdb->insert( "{$wpdb->prefix}ahm_pages", $data ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery
$data['id'] = $wpdb->insert_id;
$row = (object) $data;
} elseif ( $row->title !== $title ) {
$wpdb->update( "{$wpdb->prefix}ahm_pages", array( 'title' => $title ), array( 'id' => $row->id ), array( '%s' ), array( '%d' ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery
}
$page_id2 = $wpdb->get_var( $wpdb->prepare( "SELECT page_id2 FROM {$wpdb->prefix}ahm_events AS e INNER JOIN (SELECT id FROM {$wpdb->prefix}ahm_pages WHERE url2 = %s) AS p ON e.page_id2 = p.id LIMIT 1", $row->url2 ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery
$page_id = $row->id;
$page_id2 = $page_id2 ? $page_id2 : $page_id;
$count = 0;
foreach ( $dataset_checked as $data ) {
$add = array(
'event' => $events[ $data['event'] ],
'page_id' => $page_id,
'page_id2' => $page_id2,
'x' => $data['x'],
'y' => $data['y'],
'width' => $data['width'],
'height' => $data['height'],
'insert_at' => $data['insert_at'],
);
$wpdb->insert( "{$wpdb->prefix}ahm_events", $add ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery
++$count;
}
echo wp_json_encode( array( 'count' => $count ), JSON_NUMERIC_CHECK );
die();
}
/**
* Whether user can settings
*/
protected function can_settings() {
static $memo;
if ( ! isset( $memo ) ) {
$memo = current_user_can( 'manage_options' );
}
return $memo;
}
/**
* Whether user can report
*
* @return bool
*/
protected function can_report() {
static $memo;
if ( ! isset( $memo ) ) {
$memo = ! current_user_can( 'manage_options' );
}
return $memo;
}
/**
* Whether user can view
*
* @return bool
*/
protected function can_view() {
static $memo;
if ( ! isset( $memo ) ) {
$memo = current_user_can( 'manage_options' );
}
return $memo;
}
/**
* Action hook for wp_enqueue_scripts
*/
public function wp_enqueue_scripts() {
if ( $this->user ) {
$user = wp_get_current_user();
wp_set_current_user( $this->user->ID );
}
wp_enqueue_style( 'aurora-heatmap', plugins_url( 'style.css', __FILE__ ), array(), $this::VERSION );
$this->enqueue_report();
$this->enqueue_view();
if ( $this->user ) {
wp_set_current_user( $user->ID );
}
}
/**
* Is reporting page
*
* @return bool
*/
protected function is_reporting_page() {
if ( ! $this->options['report_non_singular'] && ! is_singular() ) {
if ( is_front_page() && ! is_paged() ) {
return true;
}
return false;
}
return true;
}
/**
* In descendant category
*
* @param array $cats Array of category IDs.
* @param object $_post Post object.
* @return bool
*/
protected function in_descendant_category( $cats, $_post = null ) {
if ( in_category( $cats, $_post ) ) {
return true;
}
foreach ( (array) $cats as $cat ) {
$descendants = get_term_children( (int) $cat, 'category' );
if ( $descendants && in_category( $descendants, $_post ) ) {
return true;
}
}
return false;
}
/**
* Euqueue Aurora Heatmap reporter
*
* @param bool $is_insert For ahm_scripts filter/shortcode.
* @return string
*/
protected function enqueue_report( $is_insert = false ) {
$q = '';
if ( ! $this->can_report() ) {
return $q;
}
if ( ! apply_filters( 'aurora_heatmap_is_reporting_page', $this->is_reporting_page() ) ) {
return $q;
}
$aurora_heatmap_reporter = array(
'_mode' => 'reporter',
'ajax_url' => admin_url( 'admin-ajax.php' ),
'action' => 'aurora_heatmap',
'reports' => implode( ',', array_keys( $this::EVENT_NAMES ) ),
'debug' => (int) $this->is_debug,
'ajax_delay_time' => (int) $this->options['ajax_delay_time'],
'ajax_interval' => apply_filters( 'aurora_heatmap_report_interval', $this->options['ajax_interval'] ),
'ajax_bulk' => apply_filters( 'aurora_heatmap_report_stacks', $this->options['ajax_bulk'] ),
);
$src = plugins_url( 'js/aurora-heatmap.min.js', __FILE__ );
if ( ! $is_insert ) {
wp_enqueue_script( 'aurora-heatmap', $src, array(), $this::VERSION, false );
wp_localize_script( 'aurora-heatmap', 'aurora_heatmap', $aurora_heatmap_reporter );
} else {
// phpcs:disable WordPress.WP.EnqueuedResources.NonEnqueuedScript
$q = '' . PHP_EOL .
'' . PHP_EOL;
// phpcs:enable WordPress.WP.EnqueuedResources.NonEnqueuedScript
}
return $q;
}
/**
* Enqueue Aurora Heatmap viewer
*
* @param bool $is_insert For ahm_scripts filter/shortcode.
* @return string
*/
protected function enqueue_view( $is_insert = false ) {
$q = '';
if ( ! $this->can_view() ) {
return $q;
}
$test = filter_input( INPUT_GET, 'aurora-heatmap' );
$test = $test ? $test : '';
$param = explode( '-', $test, 2 );
if ( 2 !== count( $param ) || ! array_key_exists( $param[0], $this::EVENT_NAMES ) ) {
return $q;
}
$event = $param[0];
$page_id = (int) $param[1];
if ( ! $page_id || ! array_key_exists( $event, $this::EVENT_NAMES ) ) {
return $q;
}
$view_width = $this::VIEW_WIDTH;
$events = $this::EVENT_NAMES;
$event_id = $events[ $event ];
$access_from = $event_id & 15;
$access_name = explode( '_', $event )[1];
$view_width = apply_filters( 'aurora_heatmap_width_' . $access_name . '_default', $view_width[ $access_from ] );
$data = $this->get_heatmap_data( $page_id, $event_id );
if ( ! $data ) {
$data = array();
}
$serialize_precision = ini_get( 'serialize_precision' );
ini_set( 'serialize_precision', '-1' ); // phpcs:ignore WordPress.PHP.IniSet.Risky
$localize = array(
'_mode' => 'viewer',
'event' => $event,
'width' => $view_width,
'count_bar' => $this->options['count_bar'],
'data' => wp_json_encode( $data, JSON_NUMERIC_CHECK ),
);
$src = plugins_url( 'js/aurora-heatmap.min.js', __FILE__ );
if ( $is_insert ) {
// phpcs:disable WordPress.WP.EnqueuedResources.NonEnqueuedScript
$q = '' . PHP_EOL .
'' . PHP_EOL;
// phpcs:enable WordPress.WP.EnqueuedResources.NonEnqueuedScript
} else {
wp_enqueue_script( 'aurora-heatmap', $src, array(), $this::VERSION, false );
wp_localize_script( 'aurora-heatmap', 'aurora_heatmap', $localize );
}
ini_set( 'serialize_precision', $serialize_precision ); // phpcs:ignore WordPress.PHP.IniSet.Risky
return $q;
}
/**
* Daily cron
*/
public function aurora_heatmap_cron_daily() {
$this->setup_daily_cron();
$this->delete_old_data( $this->options['period'] );
}
/**
* Default option values
*
* @return array
*/
protected function get_default_options() {
return array(
'activated_ver' => null,
'accuracy' => 2,
'report_non_singular' => 1,
'drawing_points' => 3000,
'count_bar' => 1,
'keep_url_query' => 0,
'keep_url_hash' => 0,
'ajax_delay_time' => 3000,
'weekly_email_content_type' => 'html',
);
}
/**
* Force option values
*
* @return array
*/
protected function get_force_options() {
return array(
'period' => 1, // Avoid huge DB consumption.
'ajax_interval' => 10,
'ajax_stacks' => 10,
'url_query_include' => array(), // Keep simplicity.
'url_query_exclude' => array(), // Keep simplicity.
'content_end_marker' => 0, // For advanced features.
'unread_threshold' => 25, // For advanced features.
'unread_minimum' => 2, // For advanced features.
'unread_warning' => 60, // For advanced features.
'weekly_email_sending' => 0, // For advanced features.
);
}
/**
* Option checker
*
* @param array $new_options New options.
* @param array $old Old options, otherwise skip the trigger on change.
*/
public function option_checker( $new_options, $old = null ) {
$default_options = $this->get_default_options();
$force_options = $this->get_force_options();
// Merge.
$options = array_merge( $default_options, $new_options, $force_options );
// Validate.
if ( $options['activated_ver'] ) {
$options['activated_ver'] = (string) $options['activated_ver'];
} elseif ( isset( $old['activated_ver'] ) ) {
$options['activated_ver'] = $old['activated_ver'];
}
$options['period'] = min( 6, max( 1, intval( $options['period'] ) ) );
$options['accuracy'] = min( 2, max( 1, intval( $options['accuracy'] ) ) );
$options['report_non_singular'] = intval( (bool) $options['report_non_singular'] );
$options['drawing_points'] = max( 0, intval( $options['drawing_points'] ) );
$options['count_bar'] = intval( (bool) $options['count_bar'] );
$options['keep_url_query'] = intval( (bool) $options['keep_url_query'] );
$options['keep_url_hash'] = intval( (bool) $options['keep_url_hash'] );
$options['ajax_delay_time'] = max( 0, intval( $options['ajax_delay_time'] ) );
// Trigger on change.
if ( $old ) {
$backup_options = $this->options;
$this->options = $options;
$old = array_merge( $default_options, $old, $force_options );
if (
$options['keep_url_query'] !== $old['keep_url_query'] ||
$options['keep_url_hash'] !== $old['keep_url_hash'] ||
$options['url_query_include'] !== $old['url_query_include'] ||
$options['url_query_exclude'] !== $old['url_query_exclude']
) {
$this->update_url2();
}
$this->options = $backup_options;
}
// Checked options.
return $options;
}
/**
* Save options
*/
protected function save_options() {
$options = array(
'accuracy' => filter_input( INPUT_POST, 'accuracy', FILTER_VALIDATE_INT ),
'report_non_singular' => filter_input( INPUT_POST, 'report_non_singular', FILTER_VALIDATE_INT ),
'drawing_points' => filter_input( INPUT_POST, 'drawing_points', FILTER_VALIDATE_INT ),
'count_bar' => filter_input( INPUT_POST, 'count_bar', FILTER_VALIDATE_INT ),
'keep_url_query' => filter_input( INPUT_POST, 'keep_url_query', FILTER_VALIDATE_BOOLEAN ),
'keep_url_hash' => filter_input( INPUT_POST, 'keep_url_hash', FILTER_VALIDATE_BOOLEAN ),
'ajax_delay_time' => filter_input( INPUT_POST, 'ajax_delay_time', FILTER_VALIDATE_INT ),
);
$this->options->save( $options );
}
/**
* Update url2
*/
protected function update_url2() {
global $wpdb;
$id = array();
$id2 = array();
$url = array();
$dict = array();
$url2_filter = $this->make_url_filter( true );
foreach ( $wpdb->get_results( "SELECT id, url FROM {$wpdb->prefix}ahm_pages" ) as $row ) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery
$id[] = $row->id;
$url2 = $url2_filter( $row->url );
$url[] = $url2;
if ( ! isset( $dict[ $url2 ] ) ) {
$dict[ $url2 ] = $row->id;
}
$id2[] = $dict[ $url2 ];
}
$count = count( $id );
if ( ! $count ) {
return;
}
$d = implode( ',', array_fill( 0, $count, '%d' ) );
$s = implode( ',', array_fill( 0, $count, '%s' ) );
$v = array_merge( $id, $url );
$wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->prefix}ahm_pages SET url2 = ELT( FIELD( id, {$d} ), {$s} )", $v ) ); // phpcs:ignore WordPress.DB
$v = array_merge( $id, $id2 );
$wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->prefix}ahm_events SET page_id2 = ELT( FIELD( page_id, {$d} ), {$d} )", $v ) ); // phpcs:ignore WordPress.DB
}
/**
* Whether the width is within the range of accuracy
*
* @param int $width Width of browser.
* @param int $event_id Event id.
* @return bool
*/
protected function is_within_accuracy( $width, $event_id ) {
static $ranges = array();
$access_from = $event_id & 15;
if ( ! isset( $ranges[ $access_from ] ) ) {
$range = $this->ar[ $access_from ][ $this->options['accuracy'] ];
if ( static::FROM_PC === $access_from ) {
$range[0] = apply_filters( 'aurora_heatmap_width_pc_from', $range[0] );
$range[1] = apply_filters( 'aurora_heatmap_width_pc_to', $range[1] );
} elseif ( static::FROM_MOBILE === $access_from ) {
$range[0] = apply_filters( 'aurora_heatmap_width_mobile_from', $range[0] );
$range[1] = apply_filters( 'aurora_heatmap_width_mobile_to', $range[1] );
}
$ranges[ $access_from ] = $range;
}
$range = $ranges[ $access_from ];
return ( $range[0] <= $width && $width <= $range[1] );
}
/**
* Make URL filter
*
* @param bool $use_options Use filtering options or not.
* @return callable
*/
protected function make_url_filter( $use_options = false ) {
global $wp;
$query_filter = null;
$fragment_filter = null;
if ( $use_options ) {
if ( ! $this->options['keep_url_query'] ) {
$include = array_flip( $wp->public_query_vars );
$query_filter = function ( $query ) use ( $include ) {
return array_intersect_key( $query, $include );
};
}
if ( ! $this->options['keep_url_hash'] ) {
$fragment_filter = function () {
return '';
};
}
}
return function ( $url ) use ( $query_filter, $fragment_filter ) {
return $this->rebuild_url( $url, $query_filter, $fragment_filter );
};
}
/**
* Rebuild URL
*
* @param string $url URL.
* @param callable $query_filter Filter callback for query string.
* @param callable $fragment_filter Fitter callback for fragment.
* @return string
*/
protected function rebuild_url( $url, $query_filter = null, $fragment_filter = null ) {
$p = wp_parse_url( $url );
if ( ! $p ) {
return false;
}
/**
* From PHP Manual comment.
*
* @link https://www.php.net/manual/ja/function.parse-url.php#106731i
*/
$scheme = isset( $p['scheme'] ) ? $p['scheme'] . '://' : '';
$host = isset( $p['host'] ) ? $p['host'] : '';
$port = isset( $p['port'] ) ? ':' . $p['port'] : '';
$user = isset( $p['user'] ) ? $p['user'] : '';
$pass = isset( $p['pass'] ) ? ':' . $p['pass'] : '';
$pass = ( $user || $pass ) ? $pass . '@' : '';
$path = isset( $p['path'] ) ? $p['path'] : '';
// Query string.
if ( isset( $p['query'] ) ) {
parse_str( $p['query'], $query );
if ( $query_filter ) {
$query = $query_filter( $query );
}
$query = $query ? '?' . http_build_query( $query ) : '';
} else {
$query = '';
}
// Fragment.
$fragment = isset( $p['fragment'] ) ? $p['fragment'] : '';
if ( $fragment && $fragment_filter ) {
$fragment = $fragment_filter( $fragment );
}
if ( $fragment ) {
$fragment = '#' . $fragment;
}
return "$scheme$user$pass$host$port$path$query$fragment";
}
/**
* Get heatmap data
*
* @param int $page_id Target URL id.
* @param int $event_id Event_id.
* @return array
*/
protected function get_heatmap_data( $page_id, $event_id ) {
switch ( $event_id ) {
case $this::CLICK_PC:
case $this::CLICK_MOBILE:
return $this->get_click_heatmap( $page_id, $event_id );
}
return array();
}
/**
* Get click heatmap
*
* @param int $page_id Target URL id.
* @param int $event_id Event_id.
* @return array
*/
protected function get_click_heatmap( $page_id, $event_id ) {
global $wpdb;
$sql = "SELECT x, y FROM {$wpdb->prefix}ahm_events WHERE event = %d AND page_id2 = %d";
$args = array( $sql, $event_id, $page_id );
if ( 0 < $this->options['drawing_points'] ) {
$args[0] .= ' ORDER BY insert_at DESC LIMIT %d';
$args[] = $this->options['drawing_points'];
}
$rows = $wpdb->get_results( $wpdb->prepare( ...$args ) ); // phpcs:ignore WordPress.DB
return array(
'points' => $rows,
'counts' => $this->get_vertical_statistics( $page_id, $event_id, 40 ),
);
}
/**
* Get page height
*
* @param int $page_id Target URL id.
* @param int $event_id Event_id.
* @return int
*/
protected function get_page_height( $page_id, $event_id ) {
global $wpdb;
// phpcs:ignore WordPress.DB.DirectDatabaseQuery
$height = $wpdb->get_var(
$wpdb->prepare(
"SELECT MAX( height ) FROM {$wpdb->prefix}ahm_events
WHERE event = %d AND page_id2 = %d AND height <= ( SELECT AVG( height ) + 2 * STD( height ) FROM {$wpdb->prefix}ahm_events WHERE event = %d AND page_id2 = %d )",
$event_id,
$page_id,
$event_id,
$page_id
)
);
if ( ! $height ) {
// phpcs:ignore WordPress.DB.DirectDatabaseQuery
$height = $wpdb->get_var(
$wpdb->prepare(
"SELECT MAX( height ) FROM {$wpdb->prefix}ahm_events WHERE event = %d AND page_id2 = %d",
$event_id,
$page_id
)
);
}
return $height;
}
/**
* Get vertical statistics
*
* @param int $page_id Target URL id.
* @param int $event_id Event_id.
* @param int $bandwidth Bandwidth of histogram.
* @return array
*/
protected function get_vertical_statistics( $page_id, $event_id, $bandwidth ) {
global $wpdb;
$page_height = $this->get_page_height( $page_id, $event_id );
if (
( $this::CLICK_PC === $event_id || $this::CLICK_MOBILE ) &&
0 < $this->options['drawing_points'] &&
// phpcs:ignore WordPress.DB.DirectDatabaseQuery
$this->options['drawing_points'] < $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->prefix}ahm_events WHERE event = %d AND page_id2 = %d", $event_id, $page_id ) )
) {
// phpcs:ignore WordPress.DB.DirectDatabaseQuery
$rows = $wpdb->get_results(
$wpdb->prepare(
"SELECT COUNT(*) AS count, y DIV %d AS iy
FROM ( SELECT * FROM {$wpdb->prefix}ahm_events WHERE event = %d AND page_id2 = %d ORDER BY insert_at DESC LIMIT %d ) AS drawing_events
GROUP BY iy",
$bandwidth,
$event_id,
$page_id,
$this->options['drawing_points']
)
);
} else {
// phpcs:ignore WordPress.DB.DirectDatabaseQuery
$rows = $wpdb->get_results(
$wpdb->prepare(
"SELECT COUNT(*) AS count, y DIV %d AS iy
FROM {$wpdb->prefix}ahm_events
WHERE event = %d
AND page_id2 = %d
GROUP BY iy",
$bandwidth,
$event_id,
$page_id
)
);
}
if ( ! $rows ) {
return array();
}
$count = ceil( $page_height / $bandwidth );
$counts = array_fill( 0, $count, 0 );
foreach ( $rows as $row ) {
if ( $row->iy < $count ) {
$counts[ $row->iy ] = $row->count;
}
}
return $counts;
}
/**
* Delete data for Aurora_Heatmap_List
*
* @param array $pageid Array of pageid.
*/
public function delete_data( $pageid ) {
global $wpdb;
$in = implode( ',', array_fill( 0, count( $pageid ), '%d' ) );
$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}ahm_events WHERE page_id2 IN ({$in})", $pageid ) ); // phpcs:ignore WordPress.DB
}
/**
* Get heatmap URL from item
*
* @param stdClsss $item Item.
* @param string $event_name event name.
* @return string URL
*/
public function get_heatmap_url( $item, $event_name ) {
$url = $item->url;
// For debug, show home_url() and imported another site heatmap.
if ( $this->is_debug ) {
static $home = '';
static $home_len = 0;
if ( ! $home ) {
$home = home_url();
if ( '/' !== substr( $home, -1 ) ) {
$home .= '/';
}
$home_len = strlen( $home );
}
if ( substr( $url, 0, $home_len ) !== $home ) {
$url = $home;
}
}
$param = ( false === strpos( $url, '?' ) ) ? '?' : '&';
$param .= 'aurora-heatmap=' . $event_name;
$param .= '-' . $item->id;
// Insert query string.
if ( false === strpos( $url, '#' ) ) {
$url .= $param;
} else {
$s = explode( '#', $url, 2 );
$url = $s[0] . $param . '#' . $s[1];
}
return $url;
}
/**
* Delete old events and not used pages.
*
* @param int $months months of retention preriod for premium version.
*/
protected function delete_old_data( $months = 1 ) {
global $wpdb;
$months; // Dummy.
// phpcs:ignore WordPress.DB.DirectDatabaseQuery
$wpdb->query( "DELETE FROM {$wpdb->prefix}ahm_events WHERE insert_at < ADDDATE( NOW(), INTERVAL - 1 MONTH )" );
$this->cleanup_pages();
}
/**
* Clean up pages
*/
protected function cleanup_pages() {
global $wpdb;
// phpcs:ignore WordPress.DB.DirectDatabaseQuery
$wpdb->query( "DELETE FROM {$wpdb->prefix}ahm_pages WHERE NOT EXISTS ( SELECT * FROM {$wpdb->prefix}ahm_events AS e WHERE {$wpdb->prefix}ahm_pages.id = e.page_id )" );
$this->update_url2();
}
/**
* Delete all data
*/
protected function delete_all() {
global $wpdb;
// phpcs:ignore WordPress.DB.DirectDatabaseQuery
$wpdb->query( "TRUNCATE TABLE {$wpdb->prefix}ahm_events" );
// phpcs:ignore WordPress.DB.DirectDatabaseQuery
$wpdb->query( "TRUNCATE TABLE {$wpdb->prefix}ahm_pages" );
}
}
/* vim: set ts=4 sw=4 sts=4 noet: */