prefix . self::TABLE;
}
public function register_hooks() {
add_action( 'admin_init', array( $this, 'maybe_install' ), 1 );
add_action( 'admin_init', array( $this, 'log_visit' ), 20 );
add_action( self::CRON_HOOK, array( $this, 'prune' ) );
if ( ! wp_next_scheduled( self::CRON_HOOK ) ) {
wp_schedule_event( time() + HOUR_IN_SECONDS, 'daily', self::CRON_HOOK );
}
}
public function maybe_install() {
if ( (int) get_option( self::SCHEMA_OPTION ) >= self::SCHEMA_VERSION ) { return; }
if ( ! AIEO_Schema_Lock::acquire( self::SCHEMA_OPTION ) ) { return; }
global $wpdb;
$table = self::table();
$collate = aieo_get_charset_collate();
$sql = "CREATE TABLE {$table} (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
user_id BIGINT UNSIGNED NOT NULL,
page VARCHAR(64) NOT NULL DEFAULT '',
tab VARCHAR(64) NOT NULL DEFAULT '',
session_id INT UNSIGNED NOT NULL DEFAULT 0,
viewed_at DATETIME NOT NULL,
PRIMARY KEY (id),
KEY user_time (user_id, viewed_at),
KEY viewed (viewed_at),
KEY tab (tab)
) {$collate};";
if ( function_exists( 'aieo_dbdelta' ) ) {
aieo_dbdelta( $sql );
} else {
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
dbDelta( $sql );
}
update_option( self::SCHEMA_OPTION, self::SCHEMA_VERSION, false );
}
public function log_visit() {
if ( wp_doing_ajax() ) { return; }
if ( isset( $_SERVER['REQUEST_METHOD'] ) && strtoupper( (string) $_SERVER['REQUEST_METHOD'] ) !== 'GET' ) { return; }
$page = isset( $_GET['page'] ) ? sanitize_key( wp_unslash( $_GET['page'] ) ) : '';
if ( $page === '' || ( strpos( $page, 'ai-eshop-' ) !== 0 && strpos( $page, 'aieo-' ) !== 0 ) ) { return; }
$uid = get_current_user_id();
if ( ! $uid ) { return; }
$tab = isset( $_GET['dmm_tab'] ) ? sanitize_key( wp_unslash( $_GET['dmm_tab'] ) ) : '';
$now = time();
$seen = (int) get_user_meta( $uid, self::SEEN_META, true );
$sess = (int) get_user_meta( $uid, self::SESSION_META, true );
if ( ! $sess || ( $now - $seen ) > self::SESSION_GAP ) {
$sess++;
update_user_meta( $uid, self::SESSION_META, $sess );
}
update_user_meta( $uid, self::SEEN_META, $now );
global $wpdb;
$wpdb->insert(
self::table(),
array(
'user_id' => $uid,
'page' => $page,
'tab' => $tab,
'session_id' => $sess,
'viewed_at' => current_time( 'mysql', true ),
),
array( '%d', '%s', '%s', '%d', '%s' )
);
if ( $page === AIEO_DMM::MENU_SLUG && $tab !== '' ) {
update_user_meta( $uid, self::LAST_TAB_META, $tab );
}
}
public static function last_tab_for_user( $uid ) {
return $uid ? (string) get_user_meta( (int) $uid, self::LAST_TAB_META, true ) : '';
}
public function prune() {
global $wpdb;
$cutoff = gmdate( 'Y-m-d H:i:s', time() - self::RETENTION_DAYS * DAY_IN_SECONDS );
$wpdb->query( $wpdb->prepare( 'DELETE FROM ' . self::table() . ' WHERE viewed_at < %s', $cutoff ) );
}
private function page_labels() {
return array(
'ai-eshop-optimizer' => __( 'Data Prep & Analytics', 'ai-eshop-optimizer' ),
'ai-eshop-dmm' => __( 'e-Shop Manager', 'ai-eshop-optimizer' ),
'ai-eshop-chat' => __( 'AI Chat', 'ai-eshop-optimizer' ),
'aieo-iapi' => __( 'Interactivity (IAPI)', 'ai-eshop-optimizer' ),
'aieo-recommendations-cache' => __( 'Recommendations Cache', 'ai-eshop-optimizer' ),
);
}
public function render_subtab() {
if ( ! current_user_can( 'activate_plugins' ) ) {
list( $aieo_pu_open, $aieo_pu_close ) = AIEO_DMM_UI::card( array( 'anchor' => 'plugin-usage-overview', 'icon' => 'activity', 'title' => __( 'Admin activity', 'ai-eshop-optimizer' ) ) );
echo $aieo_pu_open . '
' . esc_html__( 'This panel is available to site administrators only.', 'ai-eshop-optimizer' ) . '
' . $aieo_pu_close;
return;
}
$this->maybe_install();
$ranges = array( 7 => __( '7 days', 'ai-eshop-optimizer' ), 30 => __( '30 days', 'ai-eshop-optimizer' ), 90 => __( '90 days', 'ai-eshop-optimizer' ), 365 => __( '1 year', 'ai-eshop-optimizer' ), 0 => __( 'All time', 'ai-eshop-optimizer' ) );
$days = isset( $_GET['audit_days'] ) ? max( 0, min( 3650, (int) $_GET['audit_days'] ) ) : 30;
if ( ! isset( $ranges[ $days ] ) ) { $ranges[ $days ] = sprintf( __( '%d days', 'ai-eshop-optimizer' ), $days ); }
$r = $this->report( $days );
$mod_labels = AIEO_DMM::instance()->get_modules();
$page_lbl = $this->page_labels();
$base_url = AIEO_DMM::instance()->get_subtab_url( $this->get_id() );
$card_label = function ( $tab ) use ( $mod_labels ) {
return ( $tab !== '' && isset( $mod_labels[ $tab ] ) ) ? $mod_labels[ $tab ]->get_label() : ( $tab !== '' ? $tab : '—' );
};
$ago = function ( $gmt ) {
if ( empty( $gmt ) ) { return '—'; }
$ts = strtotime( $gmt . ' UTC' );
return $ts ? sprintf( __( '%s ago', 'ai-eshop-optimizer' ), human_time_diff( $ts ) ) : '—';
};
?>
1 ) );
list( $aieo_pu_open, $aieo_pu_close ) = AIEO_DMM_UI::card(
array(
'anchor' => 'plugin-usage-overview',
'icon' => 'activity',
'title' => __( 'Admin activity', 'ai-eshop-optimizer' ),
'description' => __( 'Who is using the plugin, and which panels. Admin page views only — no customer data. Visible to site administrators.', 'ai-eshop-optimizer' ),
'footnote' => sprintf( __( 'Data is kept for %d days, then pruned automatically.', 'ai-eshop-optimizer' ), self::RETENTION_DAYS ),
)
);
echo $aieo_pu_open;
?>
$lbl ) {
echo \Rooster\Admin\Ui::button( $lbl, array( 'size' => 'sm', 'variant' => ( (int) $d === (int) $days ) ? 'primary' : 'outline', 'href' => add_query_arg( 'audit_days', $d, $base_url ) ) );
}
?>
__( 'No activity in this window yet', 'ai-eshop-optimizer' ),
'text' => __( 'Usage is logged from now on.', 'ai-eshop-optimizer' ),
'compact' => true,
)
);
}
?>
(int) $row['user_id'],
'name' => $u ? $u->display_name : sprintf( __( 'User #%d (removed)', 'ai-eshop-optimizer' ), (int) $row['user_id'] ),
'role' => $u && ! empty( $u->roles ) ? implode( ', ', $u->roles ) : '',
'visits' => (int) $row['visits'],
'sessions' => (int) $row['sessions'],
'last' => $ago( $row['last_seen'] ),
'last_raw' => (string) $row['last_seen'],
'top' => isset( $r['user_top'][ $row['user_id'] ] ) ? $card_label( $r['user_top'][ $row['user_id'] ] ) : '',
);
}
AIEO_Table::card(
array(
'anchor' => 'plugin-usage-by-user',
'icon' => 'users',
'heading' => 'h2',
'title' => __( 'By user', 'ai-eshop-optimizer' ),
'description' => __( 'See who on your team opens the plugin\'s screens, how often, when they were last active and which card each uses most.', 'ai-eshop-optimizer' ),
'page_id' => 'plugin_usage',
'row_key' => 'user_id',
'rows' => $aieo_pu_users,
'columns' => array(
array( 'key' => 'name', 'label' => __( 'User', 'ai-eshop-optimizer' ) ),
array( 'key' => 'role', 'label' => __( 'Role', 'ai-eshop-optimizer' ) ),
array( 'key' => 'visits', 'label' => __( 'Views', 'ai-eshop-optimizer' ), 'type' => 'integer' ),
array( 'key' => 'sessions', 'label' => __( 'Sessions', 'ai-eshop-optimizer' ), 'type' => 'integer' ),
array( 'key' => 'last', 'label' => __( 'Last active', 'ai-eshop-optimizer' ), 'sort_key' => 'last_raw', 'nowrap' => true ),
array( 'key' => 'top', 'label' => __( 'Most-used card', 'ai-eshop-optimizer' ) ),
),
)
);
?>
'plugin-usage-most-used-cards',
'icon' => 'layout-grid',
'heading' => 'h2',
'title' => __( 'Most-used cards', 'ai-eshop-optimizer' ),
'description' => __( 'See which of the plugin\'s cards your team opens most often in the chosen period, and how many people open each.', 'ai-eshop-optimizer' ),
'page_id' => 'plugin_usage',
'row_key' => 'tab',
'rows' => array_map(
static function ( $c ) use ( $card_label ) {
return array(
'tab' => (string) $c['tab'],
'card' => $card_label( $c['tab'] ),
'visits' => (int) $c['visits'],
'users' => (int) $c['users'],
);
},
(array) $r['top_cards']
),
'columns' => array(
array( 'key' => 'card', 'label' => __( 'Card', 'ai-eshop-optimizer' ) ),
array( 'key' => 'visits', 'label' => __( 'Views', 'ai-eshop-optimizer' ), 'type' => 'integer' ),
array( 'key' => 'users', 'label' => __( 'Users', 'ai-eshop-optimizer' ), 'type' => 'integer' ),
),
)
);
?>
'plugin-usage-by-page',
'icon' => 'file-text',
'heading' => 'h2',
'title' => __( 'By page', 'ai-eshop-optimizer' ),
'description' => __( 'See how many times each admin page of the plugin was viewed in the chosen period.', 'ai-eshop-optimizer' ),
'page_id' => 'plugin_usage',
'row_key' => 'page',
'rows' => array_map(
static function ( $p ) use ( $page_lbl ) {
return array(
'page' => (string) $p['page'],
'label' => isset( $page_lbl[ $p['page'] ] ) ? $page_lbl[ $p['page'] ] : $p['page'],
'visits' => (int) $p['visits'],
);
},
(array) $r['by_page']
),
'columns' => array(
array( 'key' => 'label', 'label' => __( 'Page', 'ai-eshop-optimizer' ) ),
array( 'key' => 'visits', 'label' => __( 'Views', 'ai-eshop-optimizer' ), 'type' => 'integer' ),
),
)
);
?>
0 ) {
$where = 'viewed_at >= %s';
$args[] = gmdate( 'Y-m-d H:i:s', time() - $days * DAY_IN_SECONDS );
}
$totals = $wpdb->get_row(
$args
? $wpdb->prepare( "SELECT COUNT(*) visits, COUNT(DISTINCT user_id) users, COUNT(DISTINCT CONCAT(user_id,'-',session_id)) sessions, COUNT(DISTINCT CASE WHEN tab<>'' THEN tab END) cards FROM {$t} WHERE {$where}", $args )
: "SELECT COUNT(*) visits, COUNT(DISTINCT user_id) users, COUNT(DISTINCT CONCAT(user_id,'-',session_id)) sessions, COUNT(DISTINCT CASE WHEN tab<>'' THEN tab END) cards FROM {$t}",
ARRAY_A
);
$per_user = $wpdb->get_results(
$args
? $wpdb->prepare( "SELECT user_id, COUNT(*) visits, COUNT(DISTINCT session_id) sessions, MAX(viewed_at) last_seen FROM {$t} WHERE {$where} GROUP BY user_id ORDER BY visits DESC", $args )
: "SELECT user_id, COUNT(*) visits, COUNT(DISTINCT session_id) sessions, MAX(viewed_at) last_seen FROM {$t} GROUP BY user_id ORDER BY visits DESC",
ARRAY_A
);
$top_cards = $wpdb->get_results(
$args
? $wpdb->prepare( "SELECT tab, COUNT(*) visits, COUNT(DISTINCT user_id) users FROM {$t} WHERE {$where} AND tab<>'' GROUP BY tab ORDER BY visits DESC LIMIT 20", $args )
: "SELECT tab, COUNT(*) visits, COUNT(DISTINCT user_id) users FROM {$t} WHERE tab<>'' GROUP BY tab ORDER BY visits DESC LIMIT 20",
ARRAY_A
);
$by_page = $wpdb->get_results(
$args
? $wpdb->prepare( "SELECT page, COUNT(*) visits FROM {$t} WHERE {$where} GROUP BY page ORDER BY visits DESC", $args )
: "SELECT page, COUNT(*) visits FROM {$t} GROUP BY page ORDER BY visits DESC",
ARRAY_A
);
$rows = $wpdb->get_results(
$args
? $wpdb->prepare( "SELECT user_id, tab, COUNT(*) c FROM {$t} WHERE {$where} AND tab<>'' GROUP BY user_id, tab", $args )
: "SELECT user_id, tab, COUNT(*) c FROM {$t} WHERE tab<>'' GROUP BY user_id, tab",
ARRAY_A
);
$user_top = array();
$best = array();
foreach ( (array) $rows as $row ) {
$uid = (int) $row['user_id'];
if ( ! isset( $best[ $uid ] ) || (int) $row['c'] > $best[ $uid ] ) {
$best[ $uid ] = (int) $row['c'];
$user_top[ $uid ] = $row['tab'];
}
}
return array(
'totals' => $totals ? $totals : array( 'visits' => 0, 'users' => 0, 'sessions' => 0, 'cards' => 0 ),
'per_user' => (array) $per_user,
'top_cards' => (array) $top_cards,
'by_page' => (array) $by_page,
'user_top' => $user_top,
);
}
}