Created
May 23, 2023 19:15
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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