Commit inicial - WordPress Análisis de Precios Unitarios

- WordPress core y plugins
- Tema Twenty Twenty-Four configurado
- Plugin allow-unfiltered-html.php simplificado
- .gitignore configurado para excluir wp-config.php y uploads

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
root
2025-11-03 21:04:30 -06:00
commit a22573bf0b
24068 changed files with 4993111 additions and 0 deletions

View File

@@ -0,0 +1,57 @@
<?php
/**
* Class Google\Site_Kit\Modules\Analytics_4\Report\Metric_Filter\Between_Filter
*
* @package Google\Site_Kit\Modules\Analytics_4\Report\Metric_Filter
* @copyright 2023 Google LLC
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://sitekit.withgoogle.com
*/
namespace Google\Site_Kit\Modules\Analytics_4\Report\Filters;
use Google\Site_Kit_Dependencies\Google\Service\AnalyticsData\Filter as Google_Service_AnalyticsData_Filter;
use Google\Site_Kit_Dependencies\Google\Service\AnalyticsData\FilterExpression as Google_Service_AnalyticsData_FilterExpression;
use Google\Site_Kit_Dependencies\Google\Service\AnalyticsData\BetweenFilter as Google_Service_AnalyticsData_BetweenFilter;
use Google\Site_Kit_Dependencies\Google\Service\AnalyticsData\NumericValue;
/**
* Class for parsing the metric between filter.
*
* @since 1.111.0
* @access private
* @ignore
*/
class Between_Filter {
/**
* Converts the metric filter into the GA4 compatible metric filter expression.
*
* @since 1.111.0
*
* @param string $metric_name The metric name.
* @param integer $from_value The filter from value.
* @param integer $to_value The filter to value.
* @return Google_Service_AnalyticsData_FilterExpression The filter expression instance.
*/
public function parse_filter_expression( $metric_name, $from_value, $to_value ) {
$numeric_from_value = new NumericValue();
$numeric_from_value->setInt64Value( $from_value );
$numeric_to_value = new NumericValue();
$numeric_to_value->setInt64Value( $to_value );
$between_filter = new Google_Service_AnalyticsData_BetweenFilter();
$between_filter->setFromValue( $numeric_from_value );
$between_filter->setToValue( $numeric_to_value );
$filter = new Google_Service_AnalyticsData_Filter();
$filter->setFieldName( $metric_name );
$filter->setBetweenFilter( $between_filter );
$expression = new Google_Service_AnalyticsData_FilterExpression();
$expression->setFilter( $filter );
return $expression;
}
}

View File

@@ -0,0 +1,48 @@
<?php
/**
* Class Google\Site_Kit\Modules\Analytics_4\Report\Filters\Empty_Filter
*
* @package Google\Site_Kit\Modules\Analytics_4\Report\Filters
* @copyright 2025 Google LLC
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://sitekit.withgoogle.com
*/
namespace Google\Site_Kit\Modules\Analytics_4\Report\Filters;
use Google\Site_Kit_Dependencies\Google\Service\AnalyticsData\Filter as Google_Service_AnalyticsData_Filter;
use Google\Site_Kit_Dependencies\Google\Service\AnalyticsData\FilterExpression as Google_Service_AnalyticsData_FilterExpression;
use Google\Site_Kit_Dependencies\Google\Service\AnalyticsData\EmptyFilter as Google_Service_AnalyticsData_EmptyFilter;
/**
* Class for parsing the empty filter.
*
* @since 1.147.0
* @access private
* @ignore
*/
class Empty_Filter implements Filter {
/**
* Parses the empty filter.
*
* @since 1.147.0
* @param string $name The filter field name.
* @param string $value The filter value (not used).
*
* @return Google_Service_AnalyticsData_FilterExpression The filter expression.
*/
public function parse_filter_expression( $name, $value ) {
$empty_filter = new Google_Service_AnalyticsData_EmptyFilter();
$filter = new Google_Service_AnalyticsData_Filter();
$filter->setFieldName( $name );
$filter->setEmptyFilter( $empty_filter );
$expression = new Google_Service_AnalyticsData_FilterExpression();
$expression->setFilter( $filter );
return $expression;
}
}

View File

@@ -0,0 +1,33 @@
<?php
/**
* Class Google\Site_Kit\Modules\Analytics_4\Report\Filter
*
* @package Google\Site_Kit\Modules\Analytics_4\Report\Filters
* @copyright 2023 Google LLC
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://sitekit.withgoogle.com
*/
namespace Google\Site_Kit\Modules\Analytics_4\Report\Filters;
use Google\Site_Kit_Dependencies\Google\Service\AnalyticsData\FilterExpression as Google_Service_AnalyticsData_FilterExpression;
/**
* Interface for a filter class.
*
* @since 1.106.0
* @since 1.147.0 Moved from `Analytics_4\Report\Dimension_Filters` to `Analytics_4\Report\Filters` for use with both dimensions and metrics.
*/
interface Filter {
/**
* Converts the filter into the GA4 compatible filter expression.
*
* @since 1.106.0
*
* @param string $name Filter name.
* @param mixed $value Filter value.
* @return Google_Service_AnalyticsData_FilterExpression The filter expression instance.
*/
public function parse_filter_expression( $name, $value );
}

View File

@@ -0,0 +1,49 @@
<?php
/**
* Class Google\Site_Kit\Modules\Analytics_4\Report\In_List_Filter
*
* @package Google\Site_Kit\Modules\Analytics_4\Report\Filters
* @copyright 2023 Google LLC
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://sitekit.withgoogle.com
*/
namespace Google\Site_Kit\Modules\Analytics_4\Report\Filters;
use Google\Site_Kit_Dependencies\Google\Service\AnalyticsData\Filter as Google_Service_AnalyticsData_Filter;
use Google\Site_Kit_Dependencies\Google\Service\AnalyticsData\FilterExpression as Google_Service_AnalyticsData_FilterExpression;
use Google\Site_Kit_Dependencies\Google\Service\AnalyticsData\InListFilter as Google_Service_AnalyticsData_InListFilter;
/**
* Class for parsing the in-list filter.
*
* @since 1.106.0
* @since 1.147.0 Moved from `Analytics_4\Report\Dimension_Filters` to `Analytics_4\Report\Filters` for use with both dimensions and metrics.
* @access private
* @ignore
*/
class In_List_Filter implements Filter {
/**
* Converts the filter into the GA4 compatible filter expression.
*
* @since 1.106.0
*
* @param string $name The filter field name.
* @param mixed $value The filter value.
* @return Google_Service_AnalyticsData_FilterExpression The filter expression instance.
*/
public function parse_filter_expression( $name, $value ) {
$in_list_filter = new Google_Service_AnalyticsData_InListFilter();
$in_list_filter->setValues( $value );
$filter = new Google_Service_AnalyticsData_Filter();
$filter->setFieldName( $name );
$filter->setInListFilter( $in_list_filter );
$expression = new Google_Service_AnalyticsData_FilterExpression();
$expression->setFilter( $filter );
return $expression;
}
}

View File

@@ -0,0 +1,54 @@
<?php
/**
* Class Google\Site_Kit\Modules\Analytics_4\Report\Metric_Filter\Numeric_Filter
*
* @package Google\Site_Kit\Modules\Analytics_4\Report\Metric_Filter
* @copyright 2023 Google LLC
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://sitekit.withgoogle.com
*/
namespace Google\Site_Kit\Modules\Analytics_4\Report\Filters;
use Google\Site_Kit_Dependencies\Google\Service\AnalyticsData\Filter as Google_Service_AnalyticsData_Filter;
use Google\Site_Kit_Dependencies\Google\Service\AnalyticsData\FilterExpression as Google_Service_AnalyticsData_FilterExpression;
use Google\Site_Kit_Dependencies\Google\Service\AnalyticsData\NumericFilter as Google_Service_AnalyticsData_NumericFilter;
use Google\Site_Kit_Dependencies\Google\Service\AnalyticsData\NumericValue;
/**
* Class for parsing the metric numeric filter.
*
* @since 1.111.0
* @access private
* @ignore
*/
class Numeric_Filter {
/**
* Converts the metric filter into the GA4 compatible metric filter expression.
*
* @since 1.111.0
*
* @param string $metric_name The metric name.
* @param string $operation The filter operation.
* @param integer $value The filter value.
* @return Google_Service_AnalyticsData_FilterExpression The filter expression instance.
*/
public function parse_filter_expression( $metric_name, $operation, $value ) {
$numeric_value = new NumericValue();
$numeric_value->setInt64Value( $value );
$numeric_filter = new Google_Service_AnalyticsData_NumericFilter();
$numeric_filter->setOperation( $operation );
$numeric_filter->setValue( $numeric_value );
$filter = new Google_Service_AnalyticsData_Filter();
$filter->setFieldName( $metric_name );
$filter->setNumericFilter( $numeric_filter );
$expression = new Google_Service_AnalyticsData_FilterExpression();
$expression->setFilter( $filter );
return $expression;
}
}

View File

@@ -0,0 +1,102 @@
<?php
/**
* Class Google\Site_Kit\Modules\Analytics_4\Report\String_Filter
*
* @package Google\Site_Kit\Modules\Analytics_4\Report\Filters
* @copyright 2023 Google LLC
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://sitekit.withgoogle.com
*/
namespace Google\Site_Kit\Modules\Analytics_4\Report\Filters;
use Google\Site_Kit_Dependencies\Google\Service\AnalyticsData\Filter as Google_Service_AnalyticsData_Filter;
use Google\Site_Kit_Dependencies\Google\Service\AnalyticsData\FilterExpression as Google_Service_AnalyticsData_FilterExpression;
use Google\Site_Kit_Dependencies\Google\Service\AnalyticsData\FilterExpressionList as Google_Service_AnalyticsData_FilterExpressionList;
use Google\Site_Kit_Dependencies\Google\Service\AnalyticsData\StringFilter as Google_Service_AnalyticsData_StringFilter;
/**
* Class for parsing the string filter.
*
* @since 1.106.0
* @since 1.147.0 Moved from `Analytics_4\Report\Dimension_Filters` to `Analytics_4\Report\Filters` for use with both dimensions and metrics.
* @access private
* @ignore
*/
class String_Filter implements Filter {
/**
* Converts the filter into the GA4 compatible filter expression.
*
* @since 1.106.0
*
* @param string $name The filter field name.
* @param mixed $value The filter value.
* @return Google_Service_AnalyticsData_FilterExpression The filter expression instance.
*/
public function parse_filter_expression( $name, $value ) {
$match_type = isset( $value['matchType'] )
? $value['matchType']
: 'EXACT';
$filter_value = isset( $value['value'] )
? $value['value']
: $value;
// If there are many values for this filter, then it means that we want to find
// rows where values are included in the list of provided values. In this case,
// we need to create a nested filter expression that contains separate string filters
// for each item in the list and combined into the "OR" group.
if ( is_array( $filter_value ) ) {
$expressions = array();
foreach ( $filter_value as $value ) {
$expressions[] = $this->compose_individual_filter_expression(
$name,
$match_type,
$value
);
}
$expression_list = new Google_Service_AnalyticsData_FilterExpressionList();
$expression_list->setExpressions( $expressions );
$filter_expression = new Google_Service_AnalyticsData_FilterExpression();
$filter_expression->setOrGroup( $expression_list );
return $filter_expression;
}
// If we have a single value for the filter, then we should use just a single
// string filter expression and there is no need to create a nested one.
return $this->compose_individual_filter_expression(
$name,
$match_type,
$filter_value
);
}
/**
* Composes individual filter expression and returns it.
*
* @since 1.106.0
*
* @param string $name Filter name.
* @param string $match_type Filter match type.
* @param mixed $value Filter value.
* @return Google_Service_AnalyticsData_FilterExpression The filter expression instance.
*/
protected function compose_individual_filter_expression( $name, $match_type, $value ) {
$string_filter = new Google_Service_AnalyticsData_StringFilter();
$string_filter->setMatchType( $match_type );
$string_filter->setValue( $value );
$filter = new Google_Service_AnalyticsData_Filter();
$filter->setFieldName( $name );
$filter->setStringFilter( $string_filter );
$filter_expression = new Google_Service_AnalyticsData_FilterExpression();
$filter_expression->setFilter( $filter );
return $filter_expression;
}
}

View File

@@ -0,0 +1,173 @@
<?php
/**
* Class Google\Site_Kit\Modules\Analytics_4\Report\ReportParsers
*
* @package Google\Site_Kit\Modules\Analytics_4\Report
* @copyright 2024 Google LLC
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://sitekit.withgoogle.com
*/
namespace Google\Site_Kit\Modules\Analytics_4\Report;
use Google\Site_Kit\Core\REST_API\Data_Request;
use Google\Site_Kit\Core\Util\Date;
use Google\Site_Kit_Dependencies\Google\Service\AnalyticsData\DateRange as Google_Service_AnalyticsData_DateRange;
use Google\Site_Kit_Dependencies\Google\Service\AnalyticsData\Dimension as Google_Service_AnalyticsData_Dimension;
use Google\Site_Kit_Dependencies\Google\Service\AnalyticsData\DimensionOrderBy as Google_Service_AnalyticsData_DimensionOrderBy;
use Google\Site_Kit_Dependencies\Google\Service\AnalyticsData\MetricOrderBy as Google_Service_AnalyticsData_MetricOrderBy;
use Google\Site_Kit_Dependencies\Google\Service\AnalyticsData\OrderBy as Google_Service_AnalyticsData_OrderBy;
/**
* A class with helper methods to parse report properties
*
* @since 1.130.0
* @access private
* @ignore
*/
class ReportParsers {
/**
* Parses report dimensions received in the request params.
*
* @since 1.99.0
* @since 1.130.0 Moved into `ReportParsers` for shared used (originally between `Report` and `PivotReport`). `PivotReport` has since been removed.
*
* @param Data_Request $data Data request object.
* @return Google_Service_AnalyticsData_Dimension[] An array of AnalyticsData Dimension objects.
*/
protected function parse_dimensions( Data_Request $data ) {
$dimensions = $data['dimensions'];
if ( empty( $dimensions ) || ( ! is_string( $dimensions ) && ! is_array( $dimensions ) ) ) {
return array();
}
if ( is_string( $dimensions ) ) {
$dimensions = explode( ',', $dimensions );
} elseif ( is_array( $dimensions ) && ! wp_is_numeric_array( $dimensions ) ) { // If single object is passed.
$dimensions = array( $dimensions );
}
$dimensions = array_filter(
array_map(
function ( $dimension_def ) {
$dimension = new Google_Service_AnalyticsData_Dimension();
if ( is_string( $dimension_def ) ) {
$dimension->setName( $dimension_def );
} elseif ( is_array( $dimension_def ) && ! empty( $dimension_def['name'] ) ) {
$dimension->setName( $dimension_def['name'] );
} else {
return null;
}
return $dimension;
},
array_filter( $dimensions )
)
);
return $dimensions;
}
/**
* Parses report date ranges received in the request params.
*
* @since 1.99.0
* @since 1.130.0 Moved into `ReportParsers` for shared used (originally between `Report` and `PivotReport`). `PivotReport` has since been removed.
* @since 1.157.0 Added support for dateRangeName and compareDateRangeName parameters.
*
* @param Data_Request $data Data request object.
* @return Google_Service_AnalyticsData_DateRange[] An array of AnalyticsData DateRange objects.
*/
public function parse_dateranges( Data_Request $data ) {
$date_ranges = array();
$start_date = $data['startDate'] ?? '';
$end_date = $data['endDate'] ?? '';
if ( strtotime( $start_date ) && strtotime( $end_date ) ) {
$compare_start_date = $data['compareStartDate'] ?? '';
$compare_end_date = $data['compareEndDate'] ?? '';
$date_ranges[] = array( $start_date, $end_date );
// When using multiple date ranges, it changes the structure of the response:
// Aggregate properties (minimum, maximum, totals) will have an entry per date range.
// The rows property will have additional row entries for each date range.
if ( strtotime( $compare_start_date ) && strtotime( $compare_end_date ) ) {
$date_ranges[] = array( $compare_start_date, $compare_end_date );
}
} else {
// Default the date range to the last 28 days.
$date_ranges[] = Date::parse_date_range( 'last-28-days', 1 );
}
// Get date range names if provided.
$date_range_name = $data['dateRangeName'] ?? '';
$compare_date_range_name = $data['compareDateRangeName'] ?? '';
$date_ranges = array_map(
function ( $date_range, $index ) use ( $date_range_name, $compare_date_range_name ) {
list ( $start_date, $end_date ) = $date_range;
$date_range_obj = new Google_Service_AnalyticsData_DateRange();
$date_range_obj->setStartDate( $start_date );
$date_range_obj->setEndDate( $end_date );
// Set date range names if provided.
if ( 0 === $index && ! empty( $date_range_name ) ) {
$date_range_obj->setName( $date_range_name );
} elseif ( 1 === $index && ! empty( $compare_date_range_name ) ) {
$date_range_obj->setName( $compare_date_range_name );
}
return $date_range_obj;
},
$date_ranges,
array_keys( $date_ranges )
);
return $date_ranges;
}
/**
* Parses the orderby value of the data request into an array of AnalyticsData OrderBy object instances.
*
* @since 1.99.0
* @since 1.130.0 Moved into `ReportParsers` for shared used (originally between `Report` and `PivotReport`). `PivotReport` has since been removed.
*
* @param Data_Request $data Data request object.
* @return Google_Service_AnalyticsData_OrderBy[] An array of AnalyticsData OrderBy objects.
*/
protected function parse_orderby( Data_Request $data ) {
$orderby = $data['orderby'];
if ( empty( $orderby ) || ! is_array( $orderby ) || ! wp_is_numeric_array( $orderby ) ) {
return array();
}
$results = array_map(
function ( $order_def ) {
$order_by = new Google_Service_AnalyticsData_OrderBy();
$order_by->setDesc( ! empty( $order_def['desc'] ) );
if ( isset( $order_def['metric'] ) && isset( $order_def['metric']['metricName'] ) ) {
$metric_order_by = new Google_Service_AnalyticsData_MetricOrderBy();
$metric_order_by->setMetricName( $order_def['metric']['metricName'] );
$order_by->setMetric( $metric_order_by );
} elseif ( isset( $order_def['dimension'] ) && isset( $order_def['dimension']['dimensionName'] ) ) {
$dimension_order_by = new Google_Service_AnalyticsData_DimensionOrderBy();
$dimension_order_by->setDimensionName( $order_def['dimension']['dimensionName'] );
$order_by->setDimension( $dimension_order_by );
} else {
return null;
}
return $order_by;
},
$orderby
);
$results = array_filter( $results );
$results = array_values( $results );
return $results;
}
}

View File

@@ -0,0 +1,73 @@
<?php
/**
* Class Google\Site_Kit\Modules\Analytics_4\Report\Request
*
* @package Google\Site_Kit\Modules\Analytics_4\Report
* @copyright 2023 Google LLC
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://sitekit.withgoogle.com
*/
namespace Google\Site_Kit\Modules\Analytics_4\Report;
use Google\Site_Kit\Core\REST_API\Data_Request;
use Google\Site_Kit\Core\Validation\Exception\Invalid_Report_Dimensions_Exception;
use Google\Site_Kit\Modules\Analytics_4\Report;
use Google\Site_Kit\Modules\Analytics_4\Report\RequestHelpers;
use Google\Site_Kit_Dependencies\Google\Service\AnalyticsData\RunReportRequest as Google_Service_AnalyticsData_RunReportRequest;
use WP_Error;
/**
* Class for Analytics 4 report requests.
*
* @since 1.99.0
* @access private
* @ignore
*/
class Request extends Report {
/**
* Creates and executes a new Analytics 4 report request.
*
* @since 1.99.0
*
* @param Data_Request $data Data request object.
* @param bool $is_shared_request Determines whether the current request is shared or not.
* @return RequestInterface|WP_Error Request object on success, or WP_Error on failure.
*/
public function create_request( Data_Request $data, $is_shared_request ) {
$request_helpers = new RequestHelpers( $this->context );
$request = new Google_Service_AnalyticsData_RunReportRequest();
$request->setMetricAggregations( array( 'TOTAL', 'MINIMUM', 'MAXIMUM' ) );
if ( ! empty( $data['limit'] ) ) {
$request->setLimit( $data['limit'] );
}
$dimensions = $this->parse_dimensions( $data );
if ( ! empty( $dimensions ) ) {
if ( $is_shared_request ) {
try {
$request_helpers->validate_shared_dimensions( $dimensions );
} catch ( Invalid_Report_Dimensions_Exception $exception ) {
return new WP_Error(
'invalid_analytics_4_report_dimensions',
$exception->getMessage()
);
}
}
$request->setDimensions( (array) $dimensions );
}
$request = $request_helpers->shared_create_request( $data, $request, $is_shared_request );
$orderby = $this->parse_orderby( $data );
if ( ! empty( $orderby ) ) {
$request->setOrderBys( $orderby );
}
return $request;
}
}

View File

@@ -0,0 +1,537 @@
<?php
/**
* Class Google\Site_Kit\Modules\Analytics_4\Report\SharedRequestHelpers
*
* @package Google\Site_Kit\Modules\Analytics_4\Report
* @copyright 2024 Google LLC
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://sitekit.withgoogle.com
*/
namespace Google\Site_Kit\Modules\Analytics_4\Report;
use Google\Site_Kit\Context;
use Google\Site_Kit\Core\REST_API\Data_Request;
use Google\Site_Kit\Core\Validation\Exception\Invalid_Report_Dimensions_Exception;
use Google\Site_Kit\Core\Validation\Exception\Invalid_Report_Metrics_Exception;
use Google\Site_Kit\Core\Util\URL;
use Google\Site_Kit\Modules\Analytics_4\Report\Filters\Empty_Filter;
use Google\Site_Kit\Modules\Analytics_4\Report\Filters\In_List_Filter;
use Google\Site_Kit\Modules\Analytics_4\Report\Filters\String_Filter;
use Google\Site_Kit\Modules\Analytics_4\Report\Filters\Numeric_Filter;
use Google\Site_Kit\Modules\Analytics_4\Report\Filters\Between_Filter;
use Google\Site_Kit_Dependencies\Google\Service\AnalyticsData\Dimension as Google_Service_AnalyticsData_Dimension;
use Google\Site_Kit_Dependencies\Google\Service\AnalyticsData\FilterExpression as Google_Service_AnalyticsData_FilterExpression;
use Google\Site_Kit_Dependencies\Google\Service\AnalyticsData\FilterExpressionList as Google_Service_AnalyticsData_FilterExpressionList;
use Google\Site_Kit_Dependencies\Google\Service\AnalyticsData\RunReportRequest as Google_Service_AnalyticsData_RunReportRequest;
use Google\Site_Kit_Dependencies\Google\Service\AnalyticsData\Metric as Google_Service_AnalyticsData_Metric;
use WP_Error;
/**
* A class containing shared methods for creating AnalyticsData Report requests.
*
* @since 1.130.0
* @access private
* @ignore
*/
class RequestHelpers {
/**
* Plugin context.
*
* @since 1.130.0
* @var Context
*/
private $context;
/**
* Constructs a new instance of the class.
*
* @param Context $context Plugin context.
*/
public function __construct( $context ) {
$this->context = $context;
}
/**
* Builds a Analytics Data Report request's shared properties.
*
* @since 1.130.0
*
* @param Data_Request $data Data request object.
* @param Google_Service_AnalyticsData_RunReportRequest $request The report request object.
* @param bool $is_shared_request Determines whether the current request is shared or not.
* @return Google_Service_AnalyticsData_RunReportRequest The report request object.
*/
public function shared_create_request( Data_Request $data, $request, $is_shared_request = false ) {
$keep_empty_rows = is_array( $data->data ) && array_key_exists( 'keepEmptyRows', $data->data ) ? filter_var( $data->data['keepEmptyRows'], FILTER_VALIDATE_BOOLEAN ) : true;
$request->setKeepEmptyRows( $keep_empty_rows );
$dimension_filters = $this->parse_dimension_filters( $data );
$request->setDimensionFilter( $dimension_filters );
$metric_filters = $this->parse_metric_filters( $data );
if ( ! empty( $metric_filters ) ) {
$request->setMetricFilter( $metric_filters );
}
$report_parsers = new ReportParsers();
$date_ranges = $report_parsers->parse_dateranges( $data );
$request->setDateRanges( $date_ranges );
$metrics = $data['metrics'];
if ( is_string( $metrics ) || is_array( $metrics ) ) {
if ( is_string( $metrics ) ) {
$metrics = explode( ',', $data['metrics'] );
} elseif ( is_array( $metrics ) && ! wp_is_numeric_array( $metrics ) ) { // If single object is passed.
$metrics = array( $metrics );
}
$metrics = array_filter(
array_map(
function ( $metric_def ) {
$metric = new Google_Service_AnalyticsData_Metric();
if ( is_string( $metric_def ) ) {
$metric->setName( $metric_def );
} elseif ( is_array( $metric_def ) ) {
$metric->setName( $metric_def['name'] );
if ( ! empty( $metric_def['expression'] ) ) {
$metric->setExpression( $metric_def['expression'] );
}
} else {
return null;
}
return $metric;
},
$metrics
)
);
if ( ! empty( $metrics ) ) {
try {
$this->validate_metrics( $metrics );
} catch ( Invalid_Report_Metrics_Exception $exception ) {
return new WP_Error(
'invalid_analytics_4_report_metrics',
$exception->getMessage()
);
}
if ( $is_shared_request ) {
try {
$this->validate_shared_metrics( $metrics );
} catch ( Invalid_Report_Metrics_Exception $exception ) {
return new WP_Error(
'invalid_analytics_4_report_metrics',
$exception->getMessage()
);
}
}
$request->setMetrics( $metrics );
}
}
return $request;
}
/**
* Validates the given metrics for a report.
*
* Metrics must have valid names, matching the regular expression ^[a-zA-Z0-9_]+$ in keeping with the GA4 API.
*
* @since 1.99.0
* @since 1.130.0 Moved into RequestHelpers for shared use in reports.
*
* @param Google_Service_AnalyticsData_Metric[] $metrics The metrics to validate.
* @throws Invalid_Report_Metrics_Exception Thrown if the metrics are invalid.
*/
protected function validate_metrics( $metrics ) {
$valid_name_expression = '^[a-zA-Z0-9_]+$';
$invalid_metrics = array_map(
function ( $metric ) {
return $metric->getName();
},
array_filter(
$metrics,
function ( $metric ) use ( $valid_name_expression ) {
return ! preg_match( "#$valid_name_expression#", $metric->getName() ?? '' );
}
)
);
if ( count( $invalid_metrics ) > 0 ) {
$message = count( $invalid_metrics ) > 1 ? sprintf(
/* translators: 1: the regular expression for a valid name, 2: a comma separated list of the invalid metrics. */
__(
'Metric names should match the expression %1$s: %2$s',
'google-site-kit'
),
$valid_name_expression,
join(
/* translators: used between list items, there is a space after the comma. */
__( ', ', 'google-site-kit' ),
$invalid_metrics
)
) : sprintf(
/* translators: 1: the regular expression for a valid name, 2: the invalid metric. */
__(
'Metric name should match the expression %1$s: %2$s',
'google-site-kit'
),
$valid_name_expression,
$invalid_metrics[0]
);
throw new Invalid_Report_Metrics_Exception( $message );
}
}
/**
* Validates the report metrics for a shared request.
*
* @since 1.99.0
* @since 1.130.0 Moved into RequestHelpers for shared use in reports.
*
* @param Google_Service_AnalyticsData_Metric[] $metrics The metrics to validate.
* @throws Invalid_Report_Metrics_Exception Thrown if the metrics are invalid.
*/
protected function validate_shared_metrics( $metrics ) {
$valid_metrics = apply_filters(
'googlesitekit_shareable_analytics_4_metrics',
array(
'activeUsers',
'addToCarts',
'averageSessionDuration',
'bounceRate',
'keyEvents',
'ecommercePurchases',
'engagedSessions',
'engagementRate',
'eventCount',
'screenPageViews',
'screenPageViewsPerSession',
'sessions',
'sessionKeyEventRate',
'sessionsPerUser',
'totalAdRevenue',
'totalUsers',
)
);
$invalid_metrics = array_diff(
array_map(
function ( $metric ) {
// If there is an expression, it means the name is there as an alias, otherwise the name should be a valid metric name.
// Therefore, the expression takes precedence to the name for the purpose of allow-list validation.
return ! empty( $metric->getExpression() ) ? $metric->getExpression() : $metric->getName();
},
$metrics
),
$valid_metrics
);
if ( count( $invalid_metrics ) > 0 ) {
$message = count( $invalid_metrics ) > 1 ? sprintf(
/* translators: %s: is replaced with a comma separated list of the invalid metrics. */
__(
'Unsupported metrics requested: %s',
'google-site-kit'
),
join(
/* translators: used between list items, there is a space after the comma. */
__( ', ', 'google-site-kit' ),
$invalid_metrics
)
) : sprintf(
/* translators: %s: is replaced with the invalid metric. */
__(
'Unsupported metric requested: %s',
'google-site-kit'
),
$invalid_metrics[0]
);
throw new Invalid_Report_Metrics_Exception( $message );
}
}
/**
* Validates the report dimensions for a shared request.
*
* @since 1.99.0
* @since 1.130.0 Moved into RequestHelpers for shared use in reports.
*
* @param Google_Service_AnalyticsData_Dimension[] $dimensions The dimensions to validate.
* @throws Invalid_Report_Dimensions_Exception Thrown if the dimensions are invalid.
*/
public function validate_shared_dimensions( $dimensions ) {
$valid_dimensions = apply_filters(
'googlesitekit_shareable_analytics_4_dimensions',
array(
'audienceResourceName',
'adSourceName',
'city',
'country',
'date',
'deviceCategory',
'eventName',
'newVsReturning',
'pagePath',
'pageTitle',
'sessionDefaultChannelGroup',
'sessionDefaultChannelGrouping',
'customEvent:googlesitekit_post_author',
'customEvent:googlesitekit_post_categories',
'customEvent:googlesitekit_post_date',
'customEvent:googlesitekit_post_type',
)
);
$invalid_dimensions = array_diff(
array_map(
function ( $dimension ) {
return $dimension->getName();
},
$dimensions
),
$valid_dimensions
);
if ( count( $invalid_dimensions ) > 0 ) {
$message = count( $invalid_dimensions ) > 1 ? sprintf(
/* translators: %s: is replaced with a comma separated list of the invalid dimensions. */
__(
'Unsupported dimensions requested: %s',
'google-site-kit'
),
join(
/* translators: used between list items, there is a space after the comma. */
__( ', ', 'google-site-kit' ),
$invalid_dimensions
)
) : sprintf(
/* translators: %s: is replaced with the invalid dimension. */
__(
'Unsupported dimension requested: %s',
'google-site-kit'
),
$invalid_dimensions[0]
);
throw new Invalid_Report_Dimensions_Exception( $message );
}
}
/**
* Parses dimension filters and returns a filter expression that should be added to the report request.
*
* @since 1.106.0
* @since 1.130.0 Moved into RequestHelpers for shared use in reports.
*
* @param Data_Request $data Data request object.
* @return Google_Service_AnalyticsData_FilterExpression The filter expression to use with the report request.
*/
protected function parse_dimension_filters( Data_Request $data ) {
$expressions = array();
$reference_url = trim( $this->context->get_reference_site_url(), '/' );
$hostnames = URL::permute_site_hosts( URL::parse( $reference_url, PHP_URL_HOST ) );
$expressions[] = $this->parse_dimension_filter( 'hostName', $hostnames );
if ( ! empty( $data['url'] ) ) {
$url = str_replace( $reference_url, '', esc_url_raw( $data['url'] ) );
$expressions[] = $this->parse_dimension_filter( 'pagePath', $url );
}
if ( is_array( $data['dimensionFilters'] ) ) {
foreach ( $data['dimensionFilters'] as $key => $value ) {
$expressions[] = $this->parse_dimension_filter( $key, $value );
}
}
$filter_expression_list = new Google_Service_AnalyticsData_FilterExpressionList();
$filter_expression_list->setExpressions( array_filter( $expressions ) );
$dimension_filters = new Google_Service_AnalyticsData_FilterExpression();
$dimension_filters->setAndGroup( $filter_expression_list );
return $dimension_filters;
}
/**
* Parses and returns a single dimension filter.
*
* @since 1.106.0
* @since 1.130.0 Moved into RequestHelpers for shared use in reports.
*
* @param string $dimension_name The dimension name.
* @param mixed $dimension_value The dimension fileter settings.
* @return Google_Service_AnalyticsData_FilterExpression The filter expression instance.
*/
protected function parse_dimension_filter( $dimension_name, $dimension_value ) {
// Use the string filter type by default.
$filter_type = 'stringFilter';
if ( isset( $dimension_value['filterType'] ) ) {
// If the filterType property is provided, use the explicit filter type then.
$filter_type = $dimension_value['filterType'];
} elseif ( wp_is_numeric_array( $dimension_value ) ) {
// Otherwise, if the dimension has a numeric array of values, we should fall
// back to the "in list" filter type.
$filter_type = 'inListFilter';
}
if ( 'stringFilter' === $filter_type ) {
$filter_class = String_Filter::class;
} elseif ( 'inListFilter' === $filter_type ) {
$filter_class = In_List_Filter::class;
// Ensure that the 'inListFilter' is provided a flat array of values.
// Extract the actual values from the 'value' key if present.
if ( isset( $dimension_value['value'] ) ) {
$dimension_value = $dimension_value['value'];
}
} elseif ( 'emptyFilter' === $filter_type ) {
$filter_class = Empty_Filter::class;
} else {
return null;
}
$filter = new $filter_class();
$filter_expression = $filter->parse_filter_expression( $dimension_name, $dimension_value );
if ( ! empty( $dimension_value['notExpression'] ) ) {
$not_filter_expression = new Google_Service_AnalyticsData_FilterExpression();
$not_filter_expression->setNotExpression( $filter_expression );
return $not_filter_expression;
}
return $filter_expression;
}
/**
* Parses metric filters and returns a filter expression that should be added to the report request.
*
* @since 1.111.0
* @since 1.130.0 Moved into RequestHelpers for shared use in reports.
*
* @param Data_Request $data Data request object.
* @return Google_Service_AnalyticsData_FilterExpression The filter expression to use with the report request.
*/
protected function parse_metric_filters( Data_Request $data ) {
$expressions = array();
if ( is_array( $data['metricFilters'] ) ) {
foreach ( $data['metricFilters'] as $key => $value ) {
$expressions[] = $this->parse_metric_filter( $key, $value );
}
}
if ( ! empty( $expressions ) ) {
$filter_expression_list = new Google_Service_AnalyticsData_FilterExpressionList();
$filter_expression_list->setExpressions( array_filter( $expressions ) );
$metric_filters = new Google_Service_AnalyticsData_FilterExpression();
$metric_filters->setAndGroup( $filter_expression_list );
return $metric_filters;
}
return null;
}
/**
* Parses and returns a single metric filter.
*
* @since 1.111.0
* @since 1.130.0 Moved into RequestHelpers for shared use in reports.
*
* @param string $metric_name The metric name.
* @param mixed $metric_value The metric filter settings.
* @return Google_Service_AnalyticsData_FilterExpression The filter expression instance.
*/
protected function parse_metric_filter( $metric_name, $metric_value ) {
// Use the numeric filter type by default.
$filter_type = 'numericFilter';
if ( isset( $metric_value['filterType'] ) ) {
// If the filterType property is provided, use the explicit filter type then.
$filter_type = $metric_value['filterType'];
}
if ( 'numericFilter' === $filter_type ) {
if ( ! isset( $metric_value['operation'] ) || ! isset( $metric_value['value'] ) ) {
return null;
}
if ( ! isset( $metric_value['value']['int64Value'] ) ) {
return null;
}
$filter = new Numeric_Filter();
} elseif ( 'betweenFilter' === $filter_type ) {
if ( ! isset( $metric_value['from_value'] ) || ! isset( $metric_value['to_value'] ) ) {
return null;
}
if (
! isset( $metric_value['from_value']['int64Value'] ) ||
! isset( $metric_value['to_value']['int64Value'] )
) {
return null;
}
$filter = new Between_Filter();
} else {
return null;
}
$filter_expression = $this->get_metric_filter_expression(
$filter,
$metric_name,
$metric_value
);
return $filter_expression;
}
/**
* Returns correct filter expression instance based on the metric filter instance.
*
* @since 1.111.0
* @since 1.130.0 Moved into RequestHelpers for shared use in reports.
*
* @param Numeric_Filter|Between_Filter $filter The metric filter instance.
* @param string $metric_name The metric name.
* @param mixed $metric_value The metric filter settings.
* @return Google_Service_AnalyticsData_FilterExpression The filter expression instance.
*/
protected function get_metric_filter_expression( $filter, $metric_name, $metric_value ) {
if ( $filter instanceof Numeric_Filter ) {
$value = $metric_value['value']['int64Value'];
$filter_expression = $filter->parse_filter_expression(
$metric_name,
$metric_value['operation'],
$value
);
} elseif ( $filter instanceof Between_Filter ) {
$from_value = $metric_value['from_value']['int64Value'];
$to_value = $metric_value['to_value']['int64Value'];
$filter_expression = $filter->parse_filter_expression(
$metric_name,
$from_value,
$to_value
);
} else {
return null;
}
return $filter_expression;
}
}

View File

@@ -0,0 +1,235 @@
<?php
/**
* Class Google\Site_Kit\Modules\Analytics_4\Report\Response
*
* @package Google\Site_Kit\Modules\Analytics_4\Report
* @copyright 2023 Google LLC
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://sitekit.withgoogle.com
*/
namespace Google\Site_Kit\Modules\Analytics_4\Report;
use Google\Site_Kit\Core\REST_API\Data_Request;
use Google\Site_Kit\Modules\Analytics_4\Report;
use Google\Site_Kit_Dependencies\Google\Service\AnalyticsData\DateRange as Google_Service_AnalyticsData_DateRange;
use Google\Site_Kit_Dependencies\Google\Service\AnalyticsData\Row as Google_Service_AnalyticsData_Row;
use Google\Site_Kit_Dependencies\Google\Service\AnalyticsData\RunReportResponse as Google_Service_AnalyticsData_RunReportResponse;
/**
* Class for Analytics 4 report responses.
*
* @since 1.99.0
* @access private
* @ignore
*/
class Response extends Report {
use Row_Trait;
/**
* Parses the report response, and pads the report data with zero-data rows where rows are missing. This only applies for reports which request a single `date` dimension.
*
* @since 1.99.0
*
* @param Data_Request $data Data request object.
* @param Google_Service_AnalyticsData_RunReportResponse $response Request response.
* @return mixed Parsed response data on success, or WP_Error on failure.
*/
public function parse_response( Data_Request $data, $response ) {
// Return early if the response is not of the expected type.
if ( ! $response instanceof Google_Service_AnalyticsData_RunReportResponse ) {
return $response;
}
// Get report dimensions and return early if there is either more than one dimension or
// the only dimension is not "date".
$dimensions = $this->parse_dimensions( $data );
if ( count( $dimensions ) !== 1 || $dimensions[0]->getName() !== 'date' ) {
return $response;
}
// Get date ranges and return early if there are no date ranges for this report.
$date_ranges = $this->get_sorted_dateranges( $data );
if ( empty( $date_ranges ) ) {
return $response;
}
// Get all available dates in the report.
$existing_rows = array();
foreach ( $response->getRows() as $row ) {
$dimension_values = $row->getDimensionValues();
$range = 'date_range_0';
if ( count( $dimension_values ) > 1 ) {
// Considering this code will only be run when we are requesting a single dimension, `date`,
// the implication is that the row will _only_ have an additional dimension when multiple
// date ranges are requested.
//
// In this scenario, the dimension at index 1 will have a value of `date_range_{i}`, where
// `i` is the zero-based index of the date range.
$range = $dimension_values[1]->getValue();
}
$range = str_replace( 'date_range_', '', $range );
$date = $dimension_values[0]->getValue();
$key = self::get_response_row_key( $date, is_numeric( $range ) ? $range : false );
$existing_rows[ $key ] = $row;
}
$metric_headers = $response->getMetricHeaders();
$ranges_count = count( $date_ranges );
$multiple_ranges = $ranges_count > 1;
$rows = array();
// Add rows for the current date for each date range.
self::iterate_date_ranges(
$date_ranges,
function ( $date ) use ( &$rows, $existing_rows, $date_ranges, $ranges_count, $metric_headers, $multiple_ranges ) {
for ( $i = 0; $i < $ranges_count; $i++ ) {
$date_range_name = $date_ranges[ $i ]->getName();
if ( empty( $date_range_name ) ) {
$date_range_name = $i;
}
// Copy the existing row if it is available, otherwise create a new zero-value row.
$key = self::get_response_row_key( $date, $i );
$rows[ $key ] = isset( $existing_rows[ $key ] )
? $existing_rows[ $key ]
: $this->create_report_row( $metric_headers, $date, $multiple_ranges ? $date_range_name : false );
}
}
);
// If we have the same number of rows as in the response at the moment, then
// we can return the response without setting the new rows back into the response.
$new_rows_count = count( $rows );
if ( $new_rows_count <= $response->getRowCount() ) {
return $response;
}
// If we have multiple date ranges, we need to sort rows to have them in
// the correct order.
if ( $multiple_ranges ) {
$rows = self::sort_response_rows( $rows, $date_ranges );
}
// Set updated rows back to the response object.
$response->setRows( array_values( $rows ) );
$response->setRowCount( $new_rows_count );
return $response;
}
/**
* Gets the response row key composed from the date and the date range index values.
*
* @since 1.99.0
*
* @param string $date The date of the row to return key for.
* @param int|bool $date_range_index The date range index, or FALSE if no index is available.
* @return string The row key.
*/
protected static function get_response_row_key( $date, $date_range_index ) {
return "{$date}_{$date_range_index}";
}
/**
* Returns sorted and filtered date ranges received in the request params. All corrupted date ranges
* are ignored and not included in the returning list.
*
* @since 1.99.0
*
* @param Data_Request $data Data request object.
* @return Google_Service_AnalyticsData_DateRange[] An array of AnalyticsData DateRange objects.
*/
protected function get_sorted_dateranges( Data_Request $data ) {
$date_ranges = $this->parse_dateranges( $data );
if ( empty( $date_ranges ) ) {
return $date_ranges;
}
// Filter out all corrupted date ranges.
$date_ranges = array_filter(
$date_ranges,
function ( $range ) {
$start = strtotime( $range->getStartDate() );
$end = strtotime( $range->getEndDate() );
return ! empty( $start ) && ! empty( $end );
}
);
// Sort date ranges preserving keys to have the oldest date range at the beginning and
// the latest date range at the end.
uasort(
$date_ranges,
function ( $a, $b ) {
$a_start = strtotime( $a->getStartDate() );
$b_start = strtotime( $b->getStartDate() );
return $a_start - $b_start;
}
);
return $date_ranges;
}
/**
* Sorts response rows using the algorithm similar to the one that Analytics 4 uses internally
* and returns sorted rows.
*
* @since 1.99.0
*
* @param Google_Service_AnalyticsData_Row[] $rows The current report rows.
* @param Google_Service_AnalyticsData_DateRange[] $date_ranges The report date ranges.
* @return Google_Service_AnalyticsData_Row[] Sorted rows.
*/
protected static function sort_response_rows( $rows, $date_ranges ) {
$sorted_rows = array();
$ranges_count = count( $date_ranges );
self::iterate_date_ranges(
$date_ranges,
function ( $date, $range_index ) use ( &$sorted_rows, $ranges_count, $rows ) {
// First take the main date range row.
$key = self::get_response_row_key( $date, $range_index );
$sorted_rows[ $key ] = $rows[ $key ];
// Then take all remaining rows.
for ( $i = 0; $i < $ranges_count; $i++ ) {
if ( $i !== $range_index ) {
$key = self::get_response_row_key( $date, $i );
$sorted_rows[ $key ] = $rows[ $key ];
}
}
}
);
return $sorted_rows;
}
/**
* Iterates over the date ranges and calls callback for each date in each range.
*
* @since 1.99.0
*
* @param Google_Service_AnalyticsData_DateRange[] $date_ranges The report date ranges.
* @param callable $callback The callback to execute for each date.
*/
protected static function iterate_date_ranges( $date_ranges, $callback ) {
foreach ( $date_ranges as $date_range_index => $date_range ) {
$now = strtotime( $date_range->getStartDate() );
$end = strtotime( $date_range->getEndDate() );
do {
call_user_func(
$callback,
gmdate( 'Ymd', $now ),
$date_range_index
);
$now += DAY_IN_SECONDS;
} while ( $now <= $end );
}
}
}

View File

@@ -0,0 +1,70 @@
<?php
/**
* Class Google\Site_Kit\Modules\Analytics_4\Report\Row_Trait
*
* @package Google\Site_Kit\Modules\Analytics_4\Report
* @copyright 2023 Google LLC
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://sitekit.withgoogle.com
*/
namespace Google\Site_Kit\Modules\Analytics_4\Report;
use Google\Site_Kit_Dependencies\Google\Service\AnalyticsData\DimensionValue as Google_Service_AnalyticsData_DimensionValue;
use Google\Site_Kit_Dependencies\Google\Service\AnalyticsData\MetricHeader as Google_Service_AnalyticsData_MetricHeader;
use Google\Site_Kit_Dependencies\Google\Service\AnalyticsData\MetricValue as Google_Service_AnalyticsData_MetricValue;
use Google\Site_Kit_Dependencies\Google\Service\AnalyticsData\Row as Google_Service_AnalyticsData_Row;
/**
* A trait that adds a helper method to create report rows.
*
* @since 1.99.0
* @access private
* @ignore
*/
trait Row_Trait {
/**
* Creates and returns a new zero-value row for provided date and metrics.
*
* @since 1.99.0
*
* @param Google_Service_AnalyticsData_MetricHeader[] $metric_headers Metric headers from the report response.
* @param string $current_date The current date to create a zero-value row for.
* @param int|bool $date_range_index The date range index for the current date.
* @param string $default_value The default value to use for metric values in the row.
* @return Google_Service_AnalyticsData_Row A new zero-value row instance.
*/
protected function create_report_row( $metric_headers, $current_date, $date_range_index, $default_value = '0' ) {
$dimension_values = array();
$current_date_dimension_value = new Google_Service_AnalyticsData_DimensionValue();
$current_date_dimension_value->setValue( $current_date );
$dimension_values[] = $current_date_dimension_value;
// If we have multiple date ranges, we need to add "date_range_{i}" index to dimension values.
if ( false !== $date_range_index ) {
$date_range_dimension_value = new Google_Service_AnalyticsData_DimensionValue();
$date_range_dimension_value->setValue(
is_numeric( $date_range_index )
? "date_range_{$date_range_index}"
: $date_range_index
);
$dimension_values[] = $date_range_dimension_value;
}
$metric_values = array();
foreach ( $metric_headers as $metric_header ) {
$metric_value = new Google_Service_AnalyticsData_MetricValue();
$metric_value->setValue( $default_value );
$metric_values[] = $metric_value;
}
$row = new Google_Service_AnalyticsData_Row();
$row->setDimensionValues( $dimension_values );
$row->setMetricValues( $metric_values );
return $row;
}
}