ID ); if ( ! isset( $_REQUEST['_rt'] ) ) { return; } $rt = relevanssi_extract_rt( relevanssi_base64url_decode( $_REQUEST['_rt'] ) ); if ( is_wp_error( $rt ) ) { return; } $wpdb->query( $wpdb->prepare( "INSERT IGNORE INTO {$relevanssi_variables['tracking_table']} " . // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQL.NotPrepared '(`post_id`, `query`, `rank`, `page`, `timestamp`) VALUES (%d, %s, %d, %d, %s)', $post->ID, $rt['query'], $rt['rank'], $rt['page'], gmdate( 'c', $rt['time'] ) ) ); } /** * Extracts the values from the _rt URL parameter. * * @param string $rt The URL parameter. * * @return array|WP_Error An array of values: 'rank', 'page', 'query', and * 'time'. Returns a WP_Error if the value doesn't explode into right number of * parts. */ function relevanssi_extract_rt( string $rt ) { $rt_values = explode( '|', $rt ); if ( count( $rt_values ) != 4 ) { return new WP_Error( 'invalid-rt', __( 'Invalid click tracking value format.', 'relevanssi' ) ); } $rank = intval( $rt_values[0] ); $page = intval( $rt_values[1] ); $time = intval( $rt_values[3] ); if ( $rank === 0 || $page === 0 || $time === 0 ) { return new WP_Error( 'invalid-rt', __( 'Invalid click tracking value format.', 'relevanssi' ) ); } return array( 'rank' => $rank, 'page' => $page, 'query' => $rt_values[2], 'time' => $time, ); } /** * Adds tracking information to a permalink. * * Called from the `relevanssi_permalink` filter function to add the tracking * data to the link. * * @param string $permalink The permalink to modify. * @param object $link_post A post object, default null in which case the global * $post is used. * * @global $relevanssi_tracking_positions An array of post ID => rank pairs used * to get the post rankings. If a post does not appear in this array, the * tracking data is not added to the permalink. * @global $relevanssi_tracking_permalink A cache of permalinks to avoid doing * work that is already done. * * @return string The modified permalink. */ function relevanssi_add_tracking( string $permalink, $link_post = null ) : string { if ( 'on' !== get_option( 'relevanssi_click_tracking', 'off' ) ) { return $permalink; } if ( empty( get_search_query() ) ) { return $permalink; } if ( ! relevanssi_is_ok_to_log() ) { return $permalink; } if ( ! $link_post ) { global $post; $link_post = $post; } if ( ! relevanssi_is_front_page_id( isset( $link_post->ID ) ?? null ) ) { global $relevanssi_tracking_positions, $relevanssi_tracking_permalink; $position = $relevanssi_tracking_positions[ $link_post->ID ] ?? null; if ( ! $position ) { return $permalink; } if ( isset( $relevanssi_tracking_permalink[ $link_post->ID ] ) ) { return $relevanssi_tracking_permalink[ $link_post->ID ]; } $page = get_query_var( 'paged' ) > 0 ? get_query_var( 'paged' ) : 1; $nonce = wp_create_nonce( 'relevanssi_click_tracking_' . $link_post->ID ); $query = relevanssi_strtolower( str_replace( '|', ' ', get_search_query() ) ); $time = time(); $value = "$position|$page|$query|$time"; $permalink = esc_attr( add_query_arg( array( '_rt' => relevanssi_base64url_encode( $value ), '_rt_nonce' => $nonce, ), $permalink ) ); $relevanssi_tracking_permalink[ $link_post->ID ] = $permalink; } return $permalink; } /** * URL-friendly base64 encode. * * @param string $data String to encode. * @return string Encoded string. */ function relevanssi_base64url_encode( string $data ) : string { return rtrim( strtr( base64_encode( $data ), // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions '+/', '-_' ), '=' ); } /** * URL-friendly base64 decode. * * @param string $data String to decode. * @return string Decoded string. */ function relevanssi_base64url_decode( string $data ) : string { return base64_decode( // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions strtr( $data, '-_', '+/' ) ); } /** * Records the ranking positions for the posts found. * * Runs as the last thing (at PHP_INT_MAX) on the `relevanssi_hits_filter` hook * to record the ranking positions of each post. * * @global array $relevanssi_tracking_positions An array of post ID => rank * pairs. * * @param array $hits The hits found. * * @return array The hits found, unmodified. */ function relevanssi_record_positions( array $hits ) : array { global $relevanssi_tracking_positions; $position = 0; foreach ( $hits[0] as $hit ) { $position++; $hit = relevanssi_get_an_object( $hit )['object']; if ( ! $hit ) { continue; } if ( $hit->ID > 0 ) { $relevanssi_tracking_positions[ $hit->ID ] = $position; } elseif ( isset( $hit->term_id ) ) { $relevanssi_tracking_positions[ $hit->post_type . '_' . $hit->term_id ] = $position; } elseif ( isset( $hit->user_id ) ) { $relevanssi_tracking_positions[ 'user_' . $hit->user_id ] = $position; } } return $hits; } /** * Removes the undisplayed posts from the $relevanssi_tracking_positions array. * * Goes through the $relevanssi_tracking_positions array and only keeps the * posts that appear on the current page of results. * * @global array $relevanssi_tracking_positions An array of post ID => rank * pairs. * * @param array $hits The hits displayed. * * @return array The hits displayed, unmodified. */ function relevanssi_current_page_hits( array $hits ) : array { global $relevanssi_tracking_positions; $all_positions = $relevanssi_tracking_positions; $relevanssi_tracking_positions = array(); foreach ( $hits as $hit ) { $hit = relevanssi_get_an_object( $hit )['object']; if ( $hit->ID > 0 ) { $relevanssi_tracking_positions[ $hit->ID ] = $all_positions[ $hit->ID ]; } elseif ( isset( $hit->term_id ) ) { $id = $hit->post_type . '_' . $hit->term_id; $relevanssi_tracking_positions[ $id ] = $all_positions[ $id ]; } elseif ( isset( $hit->user_id ) ) { $id = 'user_' . $hit->user_id; $relevanssi_tracking_positions[ $id ] = $all_positions[ $id ]; } } return $hits; } /** * Creates the tracking table. * * @param string $charset_collate Character set collation. * * @return void */ function relevanssi_create_tracking_table( string $charset_collate ) { global $relevanssi_variables; $sql = 'CREATE TABLE ' . $relevanssi_variables['tracking_table'] . ' ' . "(`id` int(11) NOT NULL AUTO_INCREMENT, `post_id` int(11) NOT NULL DEFAULT '0', `query` varchar(200) NOT NULL, `rank` int(11) NOT NULL DEFAULT '0', `page` int(11) NOT NULL DEFAULT '0', `timestamp` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY id (id), UNIQUE INDEX post_id_timestamp (post_id, timestamp)) $charset_collate"; dbDelta( $sql ); } /** * Generates an array with date indices and 0 values for each date. * * Uses the `relevanssi_trim_click_logs` option to determine the length of the * date range. * * @param string $type The type of date count: 'clicks', 'log' or 'both'. * * @return array An array of 'Y-m-d' date indices. */ function relevanssi_default_date_count( string $type ) : array { global $wpdb, $relevanssi_variables; if ( 'clicks' === $type ) { $amount_of_days = get_option( 'relevanssi_trim_click_logs', 90 ); } if ( 'log' === $type ) { $amount_of_days = get_option( 'relevanssi_trim_logs', 30 ); if ( 0 === $amount_of_days ) { $amount_of_days = abs( $wpdb->get_var( "SELECT TIMESTAMPDIFF(DAY, NOW(), time) FROM {$relevanssi_variables['log_table']} ORDER BY time ASC LIMIT 1" ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQL.NotPrepared. } } if ( 'both' === $type ) { $click_days = get_option( 'relevanssi_trim_click_logs', 90 ); $log_days = get_option( 'relevanssi_trim_logs', 30 ); if ( '0' === $log_days ) { $log_days = abs( $wpdb->get_var( "SELECT TIMESTAMPDIFF(DAY, NOW(), time) FROM {$relevanssi_variables['log_table']} ORDER BY time ASC LIMIT 1" ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQL.NotPrepared. } $amount_of_days = max( $click_days, $log_days ); } $date_counts = array(); $start_date = gmdate( 'Y-m-d', strtotime( intval( $amount_of_days ) . ' days ago' ) ); $end_date = gmdate( 'Y-m-d' ); while ( strtotime( $start_date ) <= strtotime( $end_date ) ) { $date_counts[ $start_date ] = 0; $start_date = gmdate( 'Y-m-d', strtotime( '+1 days', strtotime( $start_date ) ) ); } return $date_counts; } /** * Determines what happens when a request for a post insights screen is made. * * @param array $request The $_REQUEST array to dig for parameters. * * @return bool True, if a screen was displayed and false if not. */ function relevanssi_handle_insights_screens( array $request ) : bool { if ( isset( $request['insights'] ) ) { if ( isset( $request['action'] ) && isset( $request['query'] ) && 'delete_query' === $request['action'] ) { check_admin_referer( 'relevanssi_delete_query' ); relevanssi_delete_query( $request['query'] ); } if ( isset( $request['action'] ) && isset( $request['query'] ) && 'delete_query_from_log' === $request['action'] ) { check_admin_referer( 'relevanssi_delete_query' ); relevanssi_delete_query_from_log( $request['query'] ); } relevanssi_show_insights( stripslashes( $request['insights'] ) ); return true; } if ( isset( $request['post_insights'] ) ) { relevanssi_show_post_insights( $request['post_insights'] ); return true; } return false; } /** * Deletes a query from the click tracking database. * * @param string $query The query to delete. */ function relevanssi_delete_query( string $query ) { global $wpdb, $relevanssi_variables; $deleted = $wpdb->query( $wpdb->prepare( "DELETE FROM {$relevanssi_variables['tracking_table']} WHERE query = %s", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQL.NotPrepared stripslashes( $query ) ) ); if ( $deleted ) { printf( "
%s
%s
| ( | ) |
| '; // Translators: %d is the setting for no trim (probably 0). printf( esc_html__( 'Set to %d for no trimming. The click tracking logs will be smaller than the search logs, so this value can be bigger than the value for regular logs.', 'relevanssi' ), 0 ); echo ''; ?> | |