Skip to content

Instantly share code, notes, and snippets.

@jerclarke
Created May 23, 2023 19:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jerclarke/fd78006499c345d62201a0f883c8c52a to your computer and use it in GitHub Desktop.
Save jerclarke/fd78006499c345d62201a0f883c8c52a to your computer and use it in GitHub Desktop.
AIWP Jer's Query Demo - Simple demo of Jer's custom queries using AIWP internals
<?php
/**
* Plugin Name: AIWP Jer's Query Demo
* Plugin URI: https://jerclarke.org
* Description: Simple demo of Jer's custom queries using AIWP internals
* Author: Jer Clarke
* Version: 0.1
* Author URI: https://jerclarke.org
* Text Domain: jer-aiwp-queriess
* Domain Path: /languages
*/
/**
* IN DEVELOPMENT: Demonstrate various queries to GA API via AIWP internals
*
* ! Bad queries will always kill the API connection! Make sure to explore first!
* ? QUERY EXPLORER https://ga-dev-tools.google/ga4/query-explorer/
*
* @see GA API Dimensions and Metrics docs https://developers.google.com/analytics/devguides/reporting/data/v1/api-schema
*
*
* @return void
*/
function gv_test_aiwp_queries() {
if (empty($_GET['gv_test_aiwp_queries'])) {
return;
}
$query_manager = gv_get_ga_query_manager();
if (!is_a($query_manager, 'GV_GA_Query_Manager')) {
return;
}
echo "gv_test_aiwp_queries()";
// TODO How do we handle dates? Shouldn't it be part of GV_GA_Query_Manager?
// ? GV_GA_Query_Manager::prepare_date_dimensions($from, $to) ???
$from = "30daysAgo";
$to = "yesterday";
if ( 'today' == $from || 'yesterday' == $from ) {
$dimensions = 'ga:hour';
} else if ( '365daysAgo' == $from || '1095daysAgo' == $from ) {
$dimensions = array(
'ga:year',
'ga:month'
);
} else {
$dimensions = array(
'ga:date',
'ga:dayOfWeekName'
);
}
$filters = array();
// !EXAMPLE Counts for each day of how many sessions there were
$metrics = 'ga:sessions';
$results = $query_manager->get_results($from, $to, $metrics, $dimensions, $filters, 0) ;
print_r($results);
die();
}
add_filter('wp', 'gv_test_aiwp_queries');
/**
* Get GA API results via AIWP internal functions
*
* @see GV_GA_Query_Manager->get_results() for documentation
*
* @param string $from
* @param string $to
* @param string $metrics
* @param array $dimensions
* @param array $filters
* @return array
*/
function gv_ga_get_query_results(string $from, string $to, string $metrics, array $dimensions, array $filters, int $limit) {
$query_manager = gv_get_ga_query_manager();
if (!is_a($query_manager, 'GV_GA_Query_Manager')) {
return array();
}
return $query_manager->get_results($from, $to, $metrics, $dimensions, $filters, $limit);
}
/**
* Get/Instantiate global GV_GA_Query_Manager object
*
* @return void|GV_GA_Query_Manager
*/
function gv_get_ga_query_manager() {
global $gv_ga_query_manager;
// Fail silently if the plugin isn't installed
if (!function_exists('AIWP')) {
return;
}
if (is_a($gv_ga_query_manager, 'GV_GA_Query_Manager')) {
return $gv_ga_query_manager;
}
$gv_ga_query_manager = gv_create_ga_query_manager();
return $gv_ga_query_manager;
}
/**
* Create GV_GA_Query_Manager object and handle exceptions
*
* @return void|GV_GA_Query_Manager
*/
function gv_create_ga_query_manager() {
try {
$query_manager = new GV_GA_Query_Manager();
} catch (Exception $e) {
error_log($e->getMessage());
return;
}
return $query_manager;
}
/**
* Perform queries against the GA4 API using internal AIWP functions
*
* Messy and ugly because it copies the messy internals of AIWP
* Hopefully he adds an elegant API at some point that we can switch to
*/
class GV_GA_Query_Manager {
/**
* @var AIWP_Manager
*/
var $aiwp = null;
/**
* @var string
*/
var $project_id = "";
/**
* Constructor sets up AIWP objects to be ready for queries
*
* ! Setup code copied from AIWP_Backend_Ajax->ajax_item_reports()
*
* It's the function specifically to generate the dashboard widget report.
* It does a lot of setup right before running the query, rather than
* calling generalized helper functions.
*/
public function __construct() {
$this->get_aiwp_object();
$this->set_project_id();
$this->instantiate_aiwp_gapi_controller();
$this->timeshift_gapi_controller();
}
/**
* Get GA API results via AIWP internal functions
*
* Uses the main GA "property" set up in AIWP GA4 setup.
*
* $filters has a strange format that relies on the order of the sub-array elements.
* Here are some illustrative examples:
*
* - Filter to get only posts using gv_screen_type==post
*
* $filters[] = array(
* 'customEvent:gv_screen_type', // Dimension to filter, must match one in $dimensions
* 'EXACT', // Doesn't seem to be used in AIWP code, must be EXACT??? (Never tested other values)
* 'post', // String to filter dimension by
* false, // Whether to invert the filter using NOT (never tested)
* );
*
* - Filter limit results to URLs with "/page/2/" in them (see ->get_areachart_data_ga4())
*
* $filters[] = array(
* 'ga:pagePath',
* 'EXACT',
* '/page/2/',
* false,
* );
*
* ! WARNING 2023-05-12: This relies on hack to plugin to make AIWP_GAPI_Controller->handle_corereports_ga4() public!
* The plugin has it as a private method which makes doing this impossible.
* TODO Convince AIWP author to make AIWP_GAPI_Controller->handle_corereports_ga4() public!
*
* @see GA API Dimensions and Metrics docs https://developers.google.com/analytics/devguides/reporting/data/v1/api-schema
* @see GA Query Explorer to safely figure out args https://ga-dev-tools.google/ga4/query-explorer/
*
* @param string $from GA-style date string for starting point e.g. 30daysAgo, yesterday
* @param string $to GA-style date string for ending point e.g. 30daysAgo, yesterday
* @param string $metrics GA-style list of metrics like ga:sessions, ga:activeUsers
* @param array $dimensions GA-style list of dimensions like ga:date, ga:pageTitle
* @param array $filters Uses messy array format based on element order, see above
* @return array Query results
*/
public function get_results(string $from, string $to, string $metrics, array $dimensions, array $filters, int $limit) {
if (!$from OR !$to OR !$metrics OR !$dimensions) {
error_log('ERROR: GV_GA_Query_Manager->get_results() was missing parameters, please review $from, $to, $metrics, and $dimensions');
return array();
}
// Filters can be empty or have one or more sub-arrays, carefully prepare a unique cache key
$filters_cache_key = "";
if (!empty($filters)) {
foreach ($filters AS $filter) {
$filters_cache_key .= implode("-", $filter);
}
}
$cache_key = $this->get_cache_key($this->project_id . $from . $metrics . $filters_cache_key . $limit);
/**
* Get the report from cache, or fetch a fresh copy
*
* ! Copied from AIWP_GAPI_Controller->get_areachart_data_ga4()
*/
$results = $this->aiwp->gapi_controller->handle_corereports_ga4( $this->project_id, $from, $to, $metrics, $dimensions, false, $filters, $cache_key, $limit );
// We only return ['values'], the actual data. There should also be a ['totals'] value for stats but we don't use it.
if (!is_array($results) OR !isset($results['values'])) {
error_log('ERROR: GV_GA_Query_Manager->get_results() did not receive a valid results array');
return array();
}
return $results['values'];
}
/**
* Prepare our reference to AIWP_Manager object
*
* @return void
*/
private function get_aiwp_object() {
if (is_a($this->aiwp, 'AIWP_Manager')) {
return;
}
$this->aiwp = AIWP();
if (!is_a($this->aiwp, 'AIWP_Manager')) {
throw new Exception('ERROR: GV_GA_Query_Manager Failed to initialize AIWP_Manager');
}
}
/**
* Prepare AIWP_GAPI_Controller within AIWP_Manager object
*
* ! Copied from AIWP_Backend_Ajax->ajax_item_reports() 2022-05-15
*
* @return void
*/
private function instantiate_aiwp_gapi_controller() {
if ( $this->aiwp->config->options['token'] && $this->aiwp->config->reporting_ready ) {
if ( null === $this->aiwp->gapi_controller ) {
$this->aiwp->gapi_controller = new AIWP_GAPI_Controller();
}
}
if (!is_a($this->aiwp->gapi_controller, 'AIWP_GAPI_Controller')) {
throw new Exception('ERROR: GV_GA_Query_Manager->get_results() Failed to initialize AIWP_GAPI_Controller');
return array();
}
}
/**
* Set this->project_id to the ID of the GA4 property configured in the AIWP plugin
*
* e.g. properties/336498015/dataStreams/4130567228
*
* ! Copied from AIWP_Backend_Ajax->ajax_item_reports() 2022-05-15
*
* @return string
*/
private function set_project_id() {
if (!empty($this->aiwp->config->options['webstream_jail'])) {
$this->project_id = $this->aiwp->config->options['webstream_jail'];
}
if (!$this->project_id) {
throw new Exception('ERROR: GV_GA_Query_Manager->set_project_id() could not find a project_id. Is AIWP plugin fully configured?');
}
}
/**
* Modify the AIWP gapi_controller timeshift
*
* Honestly, not sure what this does, but it was part of AIWP querying so I
* figured we should keep it.
*
* ! Copied from AIWP_Backend_Ajax->ajax_item_reports() 2022-05-15
*
* @return void
*/
private function timeshift_gapi_controller() {
// This always returns empty in my testing, both here and in the plugin during ->ajax_item_reports()
$profile_info = AIWP_Tools::get_selected_profile( $this->aiwp->config->options['ga_profiles_list'], $this->project_id );
// Not sure what [4] would contain since it's always empty, seems like it would be the time zone
if ( isset( $profile_info[4] ) ) {
$this->aiwp->gapi_controller->timeshift = $profile_info[4];
} else {
$this->aiwp->gapi_controller->timeshift = (int) current_time( 'timestamp' ) - time();
}
}
/**
* Create a numerically unique cache key ("Serial") the same way AIWP does it just to be safe
*
* ! Copied from AIWP_GAPI_Controller->get_areachart_data_ga4()
*
* @param string $unique_string
* @return string
*/
private function get_cache_key(string $unique_string) {
return 'qr2_' . $this->aiwp->gapi_controller->get_serial( $unique_string );
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment