* @version 1.7.1 */ if ( ! defined( 'ABSPATH' ) ) { exit; } /** * Aurora_Heatmap_Basic */ class Aurora_Heatmap_Basic { const SLUG = 'aurora-heatmap'; const VERSION = '1.7.1'; const PLAN = 'basic'; const FROM_PC = 0; const FROM_MOBILE = 1; const ACCESS_NAMES = array( self::FROM_PC => 'pc', self::FROM_MOBILE => 'mobile', ); const VIEW_WIDTH = array( self::FROM_PC => 1250, self::FROM_MOBILE => 375, ); const CLICK_PC = 0x10; const CLICK_MOBILE = 0x11; const EVENT_NAMES = array( 'click_pc' => self::CLICK_PC, 'click_mobile' => self::CLICK_MOBILE, ); const LIST_PER_PAGE = 25; /** * Options * * @var array|object */ protected $options = array(); /** * Accuracy Ranges * * @var array */ protected $ar = array(); /** * Init user * * @var WP_User */ protected $user = null; /** * Is debug * * @var bool */ public $is_debug = false; /** * Singleton object * * @var object */ private static $self; /** * Get instance */ public static function get_instance() { if ( ! self::$self ) { self::$self = new self(); } return self::$self; } /** * Constructor */ protected function __construct() { add_action( 'admin_init', array( &$this, 'admin_init' ) ); add_action( 'admin_menu', array( &$this, 'admin_menu' ) ); add_action( 'wp_enqueue_scripts', array( &$this, 'wp_enqueue_scripts' ) ); add_action( 'aurora_heatmap_cron_daily', array( &$this, 'aurora_heatmap_cron_daily' ) ); add_action( 'wp_ajax_aurora_heatmap', array( &$this, 'ajax_aurora_heatmap' ) ); add_action( 'wp_ajax_nopriv_aurora_heatmap', array( &$this, 'ajax_aurora_heatmap' ) ); add_action( 'wp_is_mobile', array( &$this, 'wp_is_mobile' ) ); $this->is_debug = (bool) apply_filters( 'aurora_heatmap_debug', false ); $this->options = new Aurora_Heatmap_Options( array( &$this, 'option_checker' ) ); // Set accuracy ranges. $this->ar = $this->get_ar(); if ( ! is_admin() && isset( $_GET['aurora-heatmap'] ) && $this->can_view() ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended $this->user = wp_get_current_user(); add_action( 'wp', function () { wp_set_current_user( 0 ); } ); add_action( 'shutdown', function () { wp_set_current_user( $this->user->ID ); }, 0 ); } } /** * Callback for admin_init. * * For register_script, register_style, redirect list table. */ public function admin_init() { // Activate. if ( is_admin() && in_array( get_option( 'Activated_Plugin' ), array( 'aurora-heatmap', 'aurora_heatmap' ) ) ) { delete_option( 'Activated_Plugin' ); $this::activation(); } // If activated old version or another plan, do setup. $test = $this->options['activated_ver']; $test = $test ? $test : ''; if ( version_compare( $test, $this::VERSION, '<' ) || $this::PLAN !== $this->options['activated_plan'] ) { $this->setup(); } wp_register_script( 'aurora-heatmap', plugins_url( 'js/aurora-heatmap.min.js', __FILE__ ), array( 'jquery' ), $this::VERSION, false ); wp_register_style( 'aurora-heatmap', plugins_url( 'style.css', __FILE__ ), array(), $this::VERSION ); if ( ! isset( $_SERVER['REQUEST_URI'] ) || ! strpos( $_SERVER['REQUEST_URI'], $this::SLUG ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput return; } $uri = wp_unslash( $_SERVER['REQUEST_URI'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput $p_url = wp_parse_url( $uri ); $basename = basename( $p_url['path'] ); if ( ! isset( $p_url['query'] ) ) { $p_url['query'] = array( 'page' => null ); } $query = wp_parse_args( $p_url['query'], array( 'page' => null ) ); $tab = filter_input( INPUT_GET, 'tab' ); if ( ! $tab ) { $tab = 'view'; } if ( 'options-general.php' !== $basename || $this::SLUG !== $query['page'] || ! isset( $_SERVER['REQUEST_METHOD'] ) || 'POST' !== $_SERVER['REQUEST_METHOD'] || ! ( 'view' === $tab || 'unread' === $tab ) || ! $this->can_view() ) { return; } // Build url. $url = set_url_scheme( 'http://' . ( isset( $_SERVER['HTTP_HOST'] ) ? $_SERVER['HTTP_HOST'] : '' ) . $uri ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput $remove_query = array(); $add_query = array(); $post_s = filter_input( INPUT_POST, 's' ); if ( null !== $post_s ) { $remove_query[] = 's'; $add_query['s'] = rawurlencode( $post_s ); } $post_paged = filter_input( INPUT_POST, 'paged', FILTER_VALIDATE_INT ); if ( null !== $post_paged ) { $remove_query[] = 'paged'; $add_query['paged'] = $post_paged; } $url = remove_query_arg( $remove_query, $url ); $url = add_query_arg( $add_query, $url ); // For bulk delete action. $id = filter_input( INPUT_POST, 'id', FILTER_VALIDATE_INT, FILTER_REQUIRE_ARRAY ); while ( true ) { $action = false; if ( filter_input( INPUT_POST, 'filter_action' ) ) { break; } $action = filter_input( INPUT_POST, 'action' ); if ( null !== $action && -1 !== (int) $action ) { break; } $action = filter_input( INPUT_POST, 'action2' ); if ( null !== $action && -1 !== (int) $action ) { break; } $action = false; break; } if ( $id && 'delete' === $action ) { $this->delete_data( $id ); } wp_safe_redirect( $url ); exit; } /** * Get accuracy ranges * * @return array */ public function get_ar() { $r = 0xF; $s = 0xA; $t = function ( $m, $d ) { return array( $m - $d, $m + $d ); }; $q = function ( $m, $d, $a, $b ) use ( $r, $s, $t ) { return array( 1 => $t( $m - $d, $r * $a ), 2 => $t( $m + $d, $s * $b ), ); }; return array( $q( 0x500, 0x14, $s * 2, $s / 2 ), $q( 0x172, 0x05, 3, 1 ), ); } /** * Callback for register_activation_hook */ public function activation() { $this->setup(); } /** * Setup Aurora Heatmap */ public function setup() { global $wpdb; require_once ABSPATH . 'wp-admin/includes/upgrade.php'; $charset_collate = $wpdb->get_charset_collate(); $max_index_length = 191; // phpcs:disable WordPress.DB.DirectDatabaseQuery // From Aurora Heatmap (Installer) 0.1.1 or earlier. // From Aurora Heatmap 1.0.0 or earlier. if ( ! isset( $this->options['activated_ver'] ) && $wpdb->get_row( $wpdb->prepare( 'SHOW TABLES LIKE %s', "{$wpdb->prefix}ahm_events" ) ) && $wpdb->get_row( $wpdb->prepare( 'SHOW TABLES LIKE %s', "{$wpdb->prefix}ahm_pages" ) ) ) { // If exists some data, treat as 1.0.0, else drop table. if ( $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}ahm_events" ) || $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}ahm_pages" ) ) { $this->options['activated_ver'] = '1.0.0'; } else { $wpdb->query( "DROP TABLE {$wpdb->prefix}ahm_events" ); $wpdb->query( "DROP TABLE {$wpdb->prefix}ahm_pages" ); } } // Migration. if ( isset( $this->options['activated_ver'] ) ) { // To 1.3.0. if ( version_compare( $this->options['activated_ver'], '1.3.0', '<' ) && $wpdb->get_var( "DESCRIBE {$wpdb->prefix}ahm_events original" ) ) { $wpdb->query( "ALTER TABLE {$wpdb->prefix}ahm_events CHANGE COLUMN original page_id int(8) UNSIGNED NOT NULL, ADD COLUMN page_id2 int(8) UNSIGNED NOT NULL AFTER page_id, DROP COLUMN keep_query, DROP COLUMN keep_hash, DROP COLUMN united, ADD KEY page_id2 (page_id2)" ); $wpdb->query( "UPDATE {$wpdb->prefix}ahm_events SET page_id2 = page_id" ); $wpdb->query( $wpdb->prepare( "ALTER TABLE {$wpdb->prefix}ahm_pages ADD COLUMN url2 text NOT NULL AFTER url, ADD KEY url2 (url2(%d))", $max_index_length ) ); } // To 1.4.0. if ( version_compare( $this->options['activated_ver'], '1.4.0', '<' ) ) { if ( $wpdb->get_var( "DESCRIBE {$wpdb->prefix}ahm_events accuracy" ) ) { $wpdb->query( "ALTER TABLE {$wpdb->prefix}ahm_events DROP COLUMN accuracy" ); } if ( $wpdb->get_var( "SHOW INDEX FROM {$wpdb->prefix}ahm_events WHERE KEY_NAME = 'event_accuracy'" ) ) { $wpdb->query( "DROP INDEX event_accuracy ON {$wpdb->prefix}ahm_events" ); } } } dbDelta( "CREATE TABLE {$wpdb->prefix}ahm_events ( id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT, page_id int(8) UNSIGNED NOT NULL, page_id2 int(8) UNSIGNED NOT NULL, event tinyint(3) UNSIGNED NOT NULL, x mediumint(5) UNSIGNED NOT NULL, y mediumint(7) UNSIGNED NOT NULL, width mediumint(5) UNSIGNED NOT NULL, height mediumint(7) UNSIGNED NOT NULL, insert_at datetime NOT NULL, PRIMARY KEY (id), KEY event (event), KEY page_id2 (page_id2), KEY insert_at (insert_at) ) {$charset_collate}" ); dbDelta( "CREATE TABLE {$wpdb->prefix}ahm_pages ( id int(8) UNSIGNED NOT NULL AUTO_INCREMENT, url text NOT NULL, url2 text NOT NULL, title text NOT NULL, insert_at datetime NOT NULL, update_at datetime NOT NULL, PRIMARY KEY (id), KEY url (url({$max_index_length})), KEY url2 (url2({$max_index_length})) ) {$charset_collate}" ); // phpcs:enable WordPress.DB.DirectDatabaseQuery $this->options['activated_ver'] = $this::VERSION; $this->options['activated_plan'] = $this::PLAN; // Clear unexpected click data. // phpcs:ignore WordPress.DB.DirectDatabaseQuery $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}ahm_events WHERE event IN (%d, %d) AND ( x < 1 AND y < 1 )", $this::CLICK_PC, $this::CLICK_MOBILE ) ); $this->setup_daily_cron(); $this->update_url2(); } /** * Callback for register_deactivation_hook */ public static function deactivation() { wp_clear_scheduled_hook( 'aurora_heatmap_cron_daily' ); } /** * Retrives the timezone from site settings as a `DateTimeZone` object. * * @return DateTimeZone Timezone object. */ protected static function wp_timezone() { // Since WordPress 5.3.0. if ( function_exists( 'wp_timezone' ) ) { return wp_timezone(); } // Polyfill for wp_timezone() function. $timezone_string = get_option( 'timezone_string' ); if ( $timezone_string ) { return new DateTimeZone( $timezone_string ); } $offset = (float) get_option( 'gmt_offset' ); $hours = (int) $offset; $minutes = ( $offset - $hours ); $sign = ( $offset < 0 ) ? '-' : '+'; $abs_hour = abs( $hours ); $abs_mins = abs( $minutes * 60 ); $tz_offset = sprintf( '%s%02d:%02d', $sign, $abs_hour, $abs_mins ); return new DateTimeZone( $tz_offset ); } /** * Setup daily cron */ protected function setup_daily_cron() { $tz = static::wp_timezone(); $now = new DateTime( 'now', $tz ); $am4 = new DateTime( 'T0400', $tz ); if ( $am4 < $now ) { $am4->add( new DateInterval( 'P1D' ) ); } $am4 = $am4->getTimestamp(); $next = wp_next_scheduled( 'aurora_heatmap_cron_daily' ); if ( ! $next ) { wp_schedule_event( $am4, 'daily', 'aurora_heatmap_cron_daily' ); } elseif ( $next < $am4 || $am4 + 3600 < $next ) { wp_clear_scheduled_hook( 'aurora_heatmap_cron_daily' ); wp_schedule_event( $am4, 'daily', 'aurora_heatmap_cron_daily' ); } } /** * Action hook for admin_menu */ public function admin_menu() { if ( $this->can_view() || $this->can_settings() ) { $page = add_options_page( __( 'Aurora Heatmap', 'aurora-heatmap' ), __( 'Aurora Heatmap', 'aurora-heatmap' ), 'read', $this::SLUG, array( &$this, 'print_admin_options_page' ) ); add_action( 'admin_print_styles-' . $page, function () { wp_enqueue_style( 'aurora-heatmap' ); } ); add_action( 'admin_print_scripts-' . $page, function () { $tabs = $this->get_admin_tabs(); $active_tab = filter_input( INPUT_GET, 'tab' ); if ( ! $active_tab || ! isset( $tabs[ $active_tab ] ) ) { $active_tab = key( $tabs ); } wp_localize_script( 'aurora-heatmap', 'aurora_heatmap', array( '_mode' => 'admin', 'active_tab' => $active_tab, 'click_heatmap' => __( 'Click Heatmap', 'aurora-heatmap' ), 'breakaway_heatmap' => __( 'Breakaway Heatmap', 'aurora-heatmap' ), 'attention_heatmap' => __( 'Attention Heatmap', 'aurora-heatmap' ), ) ); wp_enqueue_script( 'aurora-heatmap' ); } ); } } /** * Get admin tabs * * @return array */ protected function get_admin_tabs() { return array( 'view' => array( 'name' => __( 'Heatmap List', 'aurora-heatmap' ), 'dashicons' => 'dashicons-list-view', 'can_use' => $this->can_view(), ), 'unread' => array( 'name' => __( 'Unread Detection', 'aurora-heatmap' ), 'dashicons' => 'dashicons-flag', 'can_use' => false, ), 'settings' => array( 'name' => __( 'Settings', 'aurora-heatmap' ), 'dashicons' => 'dashicons-admin-generic', 'can_use' => $this->can_settings(), ), 'help' => array( 'name' => __( 'Premium Version Information', 'aurora-heatmap' ), 'dashicons' => 'dashicons-info', 'can_use' => true, ), ); } /** * Print checkbox * * @param string $label Label of input checkbox elements. * @param string $name Name of input checkbox elements. * @param bool $can_use Can use or not. * @param mixed $overwrite Overwrite option value or null. */ protected function print_checkbox( $label, $name, $can_use = true, $overwrite = null ) { $value = ( null === $overwrite ) ? $this->options[ $name ] : $overwrite; $checked = $value ? ' checked' : ''; if ( $can_use ) { echo '