File "class-capture.php"
Full path: /home/webcknlt/admissiontell.com/wp-content/plugins/vibes/includes/features/class-capture.php
File
size: 10.76 B
MIME-type: text/x-php
Charset: utf-8
Download Open Edit Advanced Editor &nnbsp; Back
<?php
/**
* Vibes capture
*
* Handles all captures operations.
*
* @package Features
* @author Pierre Lannoy <https://pierre.lannoy.fr/>.
* @since 1.0.0
*/
namespace Vibes\Plugin\Feature;
use Vibes\System\BrowserPerformance;
use Vibes\System\Device;
use Vibes\Plugin\Feature\Schema;
use Vibes\Plugin\Feature\Memory;
use Vibes\System\GeoIP;
use Vibes\System\Mime;
use Vibes\System\Option;
use Vibes\System\User;
use Vibes\System\IP;
use Vibes\System\Http;
use Vibes\System\WebVitals;
/**
* Define the captures functionality.
*
* Handles all captures operations.
*
* @package Features
* @author Pierre Lannoy <https://pierre.lannoy.fr/>.
* @since 1.0.0
*/
class Capture {
/**
* The acceptable types.
*
* @since 1.0.0
* @var array $types The acceptable types.
*/
protected static $types = [ 'webvital', 'resource', 'navigation' ];
/**
* Local time zone.
*
* @since 1.0.0
* @var \Vibes\System\Timezone $local_timezone The local timezone.
*/
private static $local_timezone = null;
/**
* Initialize the class and set its properties.
*
* @since 1.0.0
*/
public function __construct() {
}
/**
* Initialize static properties and hooks.
*
* @since 1.0.0
*/
public static function init() {
// phpcs:ignore
if ( ( Option::network_get( 'capture', false ) || Option::network_get( 'rcapture', false ) ) && ( (int) Option::network_get( 'sampling' ) >= mt_rand( 1, 1000 ) ) ) {
add_filter(
'script_loader_tag',
function ( $tag, $handle, $src ) {
if ( VIBES_ANALYTICS_ID === $handle ) {
return '<script type="module" src="' . esc_url( $src ) . '" id="' . $handle . '-js"></script>';
}
return $tag;
},
10,
3
);
wp_enqueue_script( VIBES_ANALYTICS_ID );
wp_localize_script(
VIBES_ANALYTICS_ID,
'analyticsSettings',
[
'restUrl' => esc_url_raw( rest_url() . VIBES_REST_NAMESPACE . '/beacon' ),
'authenticated' => ( 0 < (int) User::get_current_user_id( 0 ) ? 1 : 0 ),
'sampling' => (int) Option::network_get( 'resource_sampling' ),
'smartFilter' => Option::network_get( 'smart_filter' ) ? 1 : 0,
'multiMetrics' => Option::network_get( 'buffer' ) ? 1 : 0,
]
);
\DecaLog\Engine::eventsLogger( VIBES_SLUG )->debug( 'Capture engine started.' );
}
}
/**
* Get a pre-filled record.
*
* @param string $url The location url.
* @param integer $authent Is te call authenticated?
* @param string $type The metrics type.
* @param string $initiator Optional. The metrics type.
* @return array A pre-filled, ready to use, record.
* @since 1.0.0
*/
public static function init_record( $url, $authent, $type, $initiator = '' ) {
$url_parts = wp_parse_url( $url );
$host = '(self)';
$cleaned_enpoint = self::clean_endpoint( $host, $url_parts['path'], 50, false );
if ( array_key_exists( 'host', $url_parts ) && isset( $url_parts['host'] ) && '' !== $url_parts['host'] ) {
$host = $url_parts['host'];
}
$geoip = new GeoIP();
$record = Schema::init_record( $type );
$datetime = new \DateTime( 'now', self::$local_timezone );
$record['timestamp'] = $datetime->format( 'Y-m-d' );
$record['site'] = get_current_blog_id();
$record['endpoint'] = substr( self::clean_endpoint( $host, $url_parts['path'], 'resource' === $type ? Option::network_get( 'rcut_path' ) : Option::network_get( 'cut_path' ) ), 0, 250 );
$record['country'] = $geoip->get_iso3166_alpha2( IP::get_current() ) ?? '00';
$record['device'] = Device::get_device();
$record['class'] = Device::get_class();
$record['type'] = $type;
$record['authent'] = 1 === (int) $authent ? 1 : 0;
$record['id'] = substr( Http::top_domain( $host, false ), 0, 40 );
if ( array_key_exists( 'scheme', $url_parts ) && isset( $url_parts['scheme'] ) ) {
if ( in_array( (string) $url_parts['scheme'], Http::$extended_schemes, true ) ) {
$record['scheme'] = $url_parts['scheme'];
} else {
$record['scheme'] = 'inline';
$record['size_sum'] = mb_strlen( $cleaned_enpoint );
$record['cache_sum'] = 0;
}
} else {
$record['scheme'] = 'inline';
}
if ( array_key_exists( 'user', $url_parts ) && array_key_exists( 'pass', $url_parts ) && isset( $url_parts['user'] ) && isset( $url_parts['pass'] ) && '(self)' !== $host ) {
$record['authority'] = substr( $url_parts['user'] . ':' . $url_parts['pass'] . '@' . $host, 0, 250 );
} else {
$record['authority'] = substr( $host, 0, 250 );
}
if ( 'resource' === $type ) {
switch ( strtolower( $initiator ) ) {
case 'xmlhttprequest':
case 'beacon':
$record['mime'] = 'application/json';
break;
case 'iframe':
$record['mime'] = 'text/html';
break;
default:
$record['mime'] = Mime::guess_type( $cleaned_enpoint );
break;
}
$record['category'] = Mime::get_category( $record['mime'] );
if ( 'img' === strtolower( $initiator ) && 'unknown' === $record['category'] ) {
$record['category'] = 'image';
}
$record['mime'] = substr( $record['mime'], 0, 90 );
}
return $record;
}
/**
* Clean the endpoint.
*
* @param string $host The host for the request.
* @param string $endpoint The endpoint to clean.
* @param int $cut Optional. The number of path levels to let.
* @param boolean $filter Optional. Accepts filtering.
* @return string The cleaned endpoint.
* @since 1.0.0
*/
public static function clean_endpoint( $host, $endpoint, $cut = 3, $filter = true ) {
if ( $filter ) {
/**
* Filters the cut level.
*
* @since 1.0.0
*
* @param int $cut The number of path levels to keep.
* @param string $host The host for the request.
* @param string $endpoint The endpoint to clean.
*/
$cut = (int) apply_filters( 'vibes_path_level', $cut, $host, $endpoint );
}
if ( '/' !== substr( $endpoint, 0, 1 ) ) {
$endpoint = '/' . $endpoint;
}
$endpoint = str_replace( '/://', '/', $endpoint );
while ( 0 !== substr_count( $endpoint, '//' ) ) {
$endpoint = str_replace( '//', '/', $endpoint );
}
$cpt = 0;
$ep = '';
while ( $cpt < $cut ) {
if ( 0 === substr_count( $endpoint, '/' ) ) {
break;
}
do {
$ep = $ep . substr( $endpoint, 0, 1 );
$endpoint = substr( $endpoint, 1 );
$length = strlen( $endpoint );
} while ( ( 0 < $length ) && ( '/' !== substr( $endpoint, 0, 1 ) ) );
++$cpt;
}
return $ep;
}
/**
* Records an entry.
*
* @param array $record A record of metrics.
* @since 1.0.0
*/
public static function record( $record ) {
if ( ( 'resource' === $record['type'] && Option::network_get( 'rcapture', false ) ) || ( 'resource' !== $record['type'] && Option::network_get( 'capture', false ) ) ) {
Schema::store_statistics( $record );
}
Memory::store_statistics( $record );
}
/**
* Pre-process an entry.
*
* @param array $content The body content of the request.
* @return \WP_REST_Response
* @since 1.0.0
*/
public static function preprocess( $content ) {
if ( ( array_key_exists( 'type', $content ) && in_array( $content['type'], self::$types, true ) && array_key_exists( 'resource', $content ) && array_key_exists( 'authenticated', $content ) && array_key_exists( 'metrics', $content ) && is_array( $content['metrics'] ) ) ) {
self::single_preprocess( $content );
\DecaLog\Engine::eventsLogger( VIBES_SLUG )->debug( 'Signal received and correctly pre-processed.', [ 'code' => 202 ] );
return new \WP_REST_Response( null, 202 );
}
if ( array_key_exists( 'type', $content ) && 'multi' === $content['type'] && array_key_exists( 'metrics', $content ) && is_array( $content['metrics'] ) ) {
foreach ( $content['metrics'] as $metric ) {
$result = self::single_preprocess( $metric );
if ( true !== $result ) {
return $result;
}
}
\DecaLog\Engine::eventsLogger( VIBES_SLUG )->debug( sprintf( _n( '%d signal received and correctly pre-processed.', '%d signals received and correctly pre-processed.', count( $content['metrics'] ), 'vibes' ), count( $content['metrics'] ) ), [ 'code' => 202 ] );
return new \WP_REST_Response( null, 202 );
}
\DecaLog\Engine::eventsLogger( VIBES_SLUG )->warning( 'Malformed beacon POST request.', [ 'code' => 400 ] );
return new \WP_REST_Response( null, 400 );
}
/**
* Pre-process a single entry.
*
* @param array $content A record of metrics.
* @return true|\WP_REST_Response True if all goes OK, a rest response otherwise.
* @since 1.0.0
*/
private static function single_preprocess( $content ) {
$record = self::init_record( $content['resource'], $content['authenticated'], $content['type'], $content['initiator'] ?? '' );
foreach ( $content['metrics'] as $metric ) {
if ( ! ( is_array( $metric ) && array_key_exists( 'name', $metric ) ) ) {
\DecaLog\Engine::eventsLogger( VIBES_SLUG )->warning( 'Malformed beacon POST request.', [ 'code' => 400 ] );
return new \WP_REST_Response( null, 400 );
}
switch ( $content['type'] ) {
case 'webvital':
if ( array_key_exists( 'value', $metric ) && in_array( $metric['name'], array_merge( WebVitals::$rated_metrics, WebVitals::$unrated_metrics ), true ) ) {
$storable_value = WebVitals::get_storable_value( (string) $metric['name'], (float) $metric['value'] );
$rate_field = WebVitals::get_rate_field( (string) $metric['name'], $storable_value );
if ( 'none' !== $rate_field ) {
$record[ $metric['name'] . '_sum' ] = $storable_value;
$record[ $metric['name'] . '_' . $rate_field ] = 1;
}
}
break;
case 'resource':
case 'navigation':
if ( array_key_exists( 'start', $metric ) && array_key_exists( 'duration', $metric ) && in_array( $metric['name'], BrowserPerformance::$spans, true ) ) {
foreach ( [ 'start', 'duration' ] as $field ) {
$record[ 'span_' . $metric['name'] . '_' . $field ] = BrowserPerformance::get_storable_value( $metric['name'], (float) $metric[ $field ] );
if ( 0 > $record[ 'span_' . $metric['name'] . '_' . $field ] ) {
return true;
}
}
$record['hit'] = 1;
}
if ( array_key_exists( 'value', $metric ) && in_array( $metric['name'], BrowserPerformance::$unrated_metrics, true ) ) {
if ( ! array_key_exists( $metric['name'] . '_sum', $record ) ) {
$record[ $metric['name'] . '_sum' ] = BrowserPerformance::get_storable_value( $metric['name'], (float) $metric['value'] );
}
}
if ( array_key_exists( 'initiator', $content ) ) {
if ( 'xmlhttprequest' === $content['initiator'] ) {
$content['initiator'] = 'xhr';
}
$record['initiator'] = substr( $content['initiator'], 0, 6 );
}
break;
}
}
self::record( $record );
return true;
}
}