diff --git a/includes/Core/Modules/Module.php b/includes/Core/Modules/Module.php index 1b2e5e843f6..1ca77051ae2 100644 --- a/includes/Core/Modules/Module.php +++ b/includes/Core/Modules/Module.php @@ -753,7 +753,20 @@ function ( $item ) { * @return bool TRUE if the request is for shared data, otherwise FALSE. */ protected function is_shared_data_request( Data_Request $data ) { - $datapoint = $this->get_datapoint_definition( "{$data->method}:{$data->datapoint}" ); + $datapoint = $this->get_datapoint_definition( "{$data->method}:{$data->datapoint}" ); + + return $this->is_shared_datapoint_request( $datapoint ); + } + + /** + * Determines whether the current datapoint request is for shared data. + * + * @since n.e.x.t + * + * @param Datapoint $datapoint Datapoint instance. + * @return bool TRUE if the request is for shared data, otherwise FALSE. + */ + protected function is_shared_datapoint_request( Datapoint $datapoint ) { $oauth_client = $this->get_oauth_client_for_datapoint( $datapoint ); if ( $this->authentication->get_oauth_client() !== $oauth_client ) { diff --git a/includes/Modules/Analytics_4.php b/includes/Modules/Analytics_4.php index c8c1c306d3c..77fbc71d8ec 100644 --- a/includes/Modules/Analytics_4.php +++ b/includes/Modules/Analytics_4.php @@ -74,6 +74,9 @@ use Google\Site_Kit\Modules\Analytics_4\Datapoints\Get_Container_Destinations; use Google\Site_Kit\Modules\Analytics_4\Datapoints\Get_Enhanced_Measurement_Settings; use Google\Site_Kit\Modules\Analytics_4\Datapoints\Get_Google_Tag_Settings; +use Google\Site_Kit\Modules\Analytics_4\Datapoints\Get_Has_Property_Access; +use Google\Site_Kit\Modules\Analytics_4\Datapoints\Get_Key_Events; +use Google\Site_Kit\Modules\Analytics_4\Datapoints\Get_Report; use Google\Site_Kit\Modules\Analytics_4\Datapoints\Get_Webdatastreams; use Google\Site_Kit\Modules\Analytics_4\Datapoints\Get_Webdatastreams_Batch; use Google\Site_Kit\Modules\Analytics_4\Datapoints\Save_Audience_Settings; @@ -88,7 +91,6 @@ use Google\Site_Kit\Modules\Analytics_4\Synchronize_AdSenseLinked; use Google\Site_Kit\Modules\Analytics_4\GoogleAnalyticsAdmin\AccountProvisioningService; use Google\Site_Kit\Modules\Analytics_4\Report\Request as Analytics_4_Report_Request; -use Google\Site_Kit\Modules\Analytics_4\Report\Response as Analytics_4_Report_Response; use Google\Site_Kit\Modules\Analytics_4\Resource_Data_Availability_Date; use Google\Site_Kit\Modules\Analytics_4\Settings; use Google\Site_Kit\Modules\Analytics_4\Synchronize_AdsLinked; @@ -97,10 +99,6 @@ use Google\Site_Kit\Modules\Analytics_4\Web_Tag; use Google\Site_Kit_Dependencies\Google\Model as Google_Model; use Google\Site_Kit_Dependencies\Google\Service\AnalyticsData as Google_Service_AnalyticsData; -use Google\Site_Kit_Dependencies\Google\Service\AnalyticsData\RunReportRequest as Google_Service_AnalyticsData_RunReportRequest; -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\Metric as Google_Service_AnalyticsData_Metric; use Google\Site_Kit_Dependencies\Google\Service\GoogleAnalyticsAdmin as Google_Service_GoogleAnalyticsAdmin; use Google\Site_Kit_Dependencies\Google\Service\GoogleAnalyticsAdmin\GoogleAnalyticsAdminV1betaDataStream; use Google\Site_Kit_Dependencies\Google\Service\GoogleAnalyticsAdmin\GoogleAnalyticsAdminV1betaDataStreamWebStreamData; @@ -118,6 +116,7 @@ use Google\Site_Kit\Modules\Analytics_4\Conversion_Reporting\Conversion_Reporting_New_Badge_Events_Sync; use Google\Site_Kit\Modules\Analytics_4\Conversion_Reporting\Conversion_Reporting_Provider; use Google\Site_Kit\Modules\Analytics_4\Reset_Audiences; +use Google\Site_Kit\Core\Modules\Datapoint; use stdClass; use WP_Error; use WP_Post; @@ -730,9 +729,13 @@ protected function get_datapoint_definitions() { ), ) ), - 'GET:key-events' => array( - 'service' => 'analyticsadmin', - 'shareable' => true, + 'GET:key-events' => new Get_Key_Events( + array( + 'service' => function () { + return $this->get_service( 'analyticsadmin' ); + }, + 'settings' => $this->get_settings(), + ) ), 'POST:create-account-ticket' => new Create_Account_Ticket( array( @@ -777,10 +780,24 @@ protected function get_datapoint_definitions() { ), 'GET:properties' => array( 'service' => 'analyticsadmin' ), 'GET:property' => array( 'service' => 'analyticsadmin' ), - 'GET:has-property-access' => array( 'service' => 'analyticsdata' ), - 'GET:report' => array( - 'service' => 'analyticsdata', - 'shareable' => true, + 'GET:has-property-access' => new Get_Has_Property_Access( + array( + 'service' => function () { + return $this->get_service( 'analyticsdata' ); + }, + ) + ), + 'GET:report' => new Get_Report( + array( + 'service' => function () { + return $this->get_service( 'analyticsdata' ); + }, + 'settings' => $this->get_settings(), + 'context' => $this->context, + 'is_shared_request' => function ( Datapoint $datapoint ) { + return $this->is_shared_datapoint_request( $datapoint ); + }, + ), ), 'GET:batch-report' => array( 'service' => 'analyticsdata', @@ -1295,58 +1312,6 @@ protected function create_data_request( Data_Request $data ) { } return $this->get_service( 'analyticsadmin' )->properties->get( self::normalize_property_id( $data['propertyID'] ) ); - case 'GET:has-property-access': - if ( ! isset( $data['propertyID'] ) ) { - throw new Missing_Required_Param_Exception( 'propertyID' ); - } - - // A simple way to check for property access is to attempt a minimal report request. - // If the user does not have access, this will return a 403 error. - $request = new Google_Service_AnalyticsData_RunReportRequest(); - $request->setDimensions( array( new Google_Service_AnalyticsData_Dimension( array( 'name' => 'date' ) ) ) ); - $request->setMetrics( array( new Google_Service_AnalyticsData_Metric( array( 'name' => 'sessions' ) ) ) ); - $request->setDateRanges( - array( - new Google_Service_AnalyticsData_DateRange( - array( - 'start_date' => 'yesterday', - 'end_date' => 'today', - ) - ), - ) - ); - $request->setLimit( 0 ); - - return $this->get_analyticsdata_service()->properties->runReport( $data['propertyID'], $request ); - case 'GET:report': - if ( empty( $data['metrics'] ) ) { - return new WP_Error( - 'missing_required_param', - /* translators: %s: Missing parameter name */ - sprintf( __( 'Request parameter is empty: %s.', 'google-site-kit' ), 'metrics' ), - array( 'status' => 400 ) - ); - } - - $settings = $this->get_settings()->get(); - if ( empty( $settings['propertyID'] ) ) { - return new WP_Error( - 'missing_required_setting', - __( 'No connected Google Analytics property ID.', 'google-site-kit' ), - array( 'status' => 500 ) - ); - } - - $report = new Analytics_4_Report_Request( $this->context ); - $request = $report->create_request( $data, $this->is_shared_data_request( $data ) ); - if ( is_wp_error( $request ) ) { - return $request; - } - - $property_id = self::normalize_property_id( $settings['propertyID'] ); - $request->setProperty( $property_id ); - - return $this->get_analyticsdata_service()->properties->runReport( $property_id, $request ); case 'GET:batch-report': if ( empty( $data['requests'] ) ) { @@ -1399,22 +1364,6 @@ protected function create_data_request( Data_Request $data ) { $property_id, $batch_request ); - case 'GET:key-events': - $settings = $this->get_settings()->get(); - if ( empty( $settings['propertyID'] ) ) { - return new WP_Error( - 'missing_required_setting', - __( 'No connected Google Analytics property ID.', 'google-site-kit' ), - array( 'status' => 500 ) - ); - } - - $analyticsadmin = $this->get_service( 'analyticsadmin' ); - $property_id = self::normalize_property_id( $settings['propertyID'] ); - - return $analyticsadmin - ->properties_keyEvents // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase - ->listPropertiesKeyEvents( $property_id ); } return parent::create_data_request( $data ); @@ -1441,11 +1390,6 @@ protected function parse_data_response( Data_Request $data, $response ) { ); case 'GET:property': return self::filter_property_with_ids( $response ); - case 'GET:key-events': - return (array) $response->getKeyEvents(); - case 'GET:report': - $report = new Analytics_4_Report_Response( $this->context ); - return $report->parse_response( $data, $response ); } return parent::parse_data_response( $data, $response ); diff --git a/includes/Modules/Analytics_4/Datapoints/Get_Has_Property_Access.php b/includes/Modules/Analytics_4/Datapoints/Get_Has_Property_Access.php new file mode 100644 index 00000000000..d6f98f80e07 --- /dev/null +++ b/includes/Modules/Analytics_4/Datapoints/Get_Has_Property_Access.php @@ -0,0 +1,77 @@ +setDimensions( array( new Google_Service_AnalyticsData_Dimension( array( 'name' => 'date' ) ) ) ); + $request->setMetrics( array( new Google_Service_AnalyticsData_Metric( array( 'name' => 'sessions' ) ) ) ); + $request->setDateRanges( + array( + new Google_Service_AnalyticsData_DateRange( + array( + 'start_date' => 'yesterday', + 'end_date' => 'today', + ) + ), + ) + ); + $request->setLimit( 0 ); + + return $this->get_service()->properties->runReport( $data['propertyID'], $request ); + } + + /** + * Parses a response. + * + * @since n.e.x.t + * + * @param mixed $response Request response. + * @param Data_Request $data Data request object. + * @return mixed The original response without any modifications. + */ + public function parse_response( $response, Data_Request $data ) { + return $response; + } +} diff --git a/includes/Modules/Analytics_4/Datapoints/Get_Key_Events.php b/includes/Modules/Analytics_4/Datapoints/Get_Key_Events.php new file mode 100644 index 00000000000..69c73fd7c4c --- /dev/null +++ b/includes/Modules/Analytics_4/Datapoints/Get_Key_Events.php @@ -0,0 +1,88 @@ +settings = $definition['settings']; + } + + /** + * Creates a request object. + * + * @since n.e.x.t + * + * @param Data_Request $data Data request object. + * @return mixed Request object on success, or WP_Error on failure. + */ + public function create_request( Data_Request $data ) { + $settings = $this->settings->get(); + + if ( empty( $settings['propertyID'] ) ) { + return new WP_Error( + 'missing_required_setting', + __( 'No connected Google Analytics property ID.', 'google-site-kit' ), + array( 'status' => 500 ) + ); + } + + $property_id = Analytics_4::normalize_property_id( $settings['propertyID'] ); + + return $this->get_service() + ->properties_keyEvents // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase + ->listPropertiesKeyEvents( $property_id ); + } + + /** + * Parses a response. + * + * @since n.e.x.t + * + * @param mixed $response Request response. + * @param Data_Request $data Data request object. + * @return GoogleAnalyticsAdminV1betaKeyEvent[] Array of key events. + */ + public function parse_response( $response, Data_Request $data ) { + return (array) $response->getKeyEvents(); + } +} diff --git a/includes/Modules/Analytics_4/Datapoints/Get_Report.php b/includes/Modules/Analytics_4/Datapoints/Get_Report.php new file mode 100644 index 00000000000..a3f0bd809fe --- /dev/null +++ b/includes/Modules/Analytics_4/Datapoints/Get_Report.php @@ -0,0 +1,123 @@ +settings = $definition['settings']; + $this->context = $definition['context']; + $this->is_shared_request = $definition['is_shared_request']; + } + + /** + * Creates a request object. + * + * @since n.e.x.t + * + * @param Data_Request $data Data request object. + * @return mixed Request object on success, or WP_Error on failure. + */ + public function create_request( Data_Request $data ) { + if ( empty( $data['metrics'] ) ) { + return new WP_Error( + 'missing_required_param', + /* translators: %s: Missing parameter name */ + sprintf( __( 'Request parameter is empty: %s.', 'google-site-kit' ), 'metrics' ), + array( 'status' => 400 ) + ); + } + + $settings = $this->settings->get(); + + if ( empty( $settings['propertyID'] ) ) { + return new WP_Error( + 'missing_required_setting', + __( 'No connected Google Analytics property ID.', 'google-site-kit' ), + array( 'status' => 500 ) + ); + } + + $report = new Analytics_4_Report_Request( $this->context ); + $request = $report->create_request( $data, ( $this->is_shared_request )( $this ) ); + if ( is_wp_error( $request ) ) { + return $request; + } + + $property_id = Analytics_4::normalize_property_id( $settings['propertyID'] ); + $request->setProperty( $property_id ); + + return $this->get_service()->properties->runReport( $property_id, $request ); + } + + /** + * Parses a response. + * + * @since n.e.x.t + * + * @param mixed $response Request response. + * @param Data_Request $data Data request object. + * @return mixed Parsed response data on success, or WP_Error on failure. + */ + public function parse_response( $response, Data_Request $data ) { + $report = new Analytics_4_Report_Response( $this->context ); + return $report->parse_response( $data, $response ); + } +} diff --git a/tests/phpunit/integration/Modules/Analytics_4/Datapoints/Get_Has_Property_AccessTest.php b/tests/phpunit/integration/Modules/Analytics_4/Datapoints/Get_Has_Property_AccessTest.php new file mode 100644 index 00000000000..2eb6301b197 --- /dev/null +++ b/tests/phpunit/integration/Modules/Analytics_4/Datapoints/Get_Has_Property_AccessTest.php @@ -0,0 +1,138 @@ +factory()->user->create_and_get( array( 'role' => 'administrator' ) ); + $user_options = new User_Options( $context, $user->ID ); + $authentication = new Authentication( $context, $options, $user_options ); + $this->analytics = new Analytics_4( $context, $options, $user_options, $authentication ); + + $this->analytics->get_client()->withDefer( true ); + + $service = new Google_Service_AnalyticsData( $this->analytics->get_client() ); + + $this->datapoint = new Get_Has_Property_Access( + array( + 'service' => function () use ( $service ) { + return $service; + }, + ), + ); + + FakeHttp::fake_google_http_handler( + $this->analytics->get_client(), + function ( Request $request ) { + $this->get_report_request = $request; + + $response = new Google_Service_AnalyticsData_RunReportResponse(); + + return new FulfilledPromise( new Response( 200, array(), json_encode( $response ) ) ); + } + ); + + wp_set_current_user( $user->ID ); + } + + public function test_create_request__requires_property_id_param() { + $data = array(); + + $data_request = new Data_Request( 'GET', 'modules', 'analytics-4', 'has-property-access', $data ); + + try { + $this->datapoint->create_request( $data_request ); + $this->fail( 'Expected Missing_Required_Param_Exception to be thrown.' ); + } catch ( \Exception $e ) { + $this->assertInstanceOf( Missing_Required_Param_Exception::class, $e, 'The datapoint should throw Missing_Required_Param_Exception when the `propertyID` parameter is missing.' ); + } + } + + public function test_create_request() { + $this->get_report_request = null; + + $data = array( + 'propertyID' => '12345', + ); + + $data_request = new Data_Request( 'GET', 'modules', 'analytics-4', 'has-property-access', $data ); + + $request = $this->datapoint->create_request( $data_request ); + + $result = $this->analytics->get_client()->execute( $request ); + + $this->assertEquals( + 'https://analyticsdata.googleapis.com/v1beta/12345:runReport', + $this->get_report_request->getUri(), + 'The request should be made to the correct endpoint.' + ); + } + + public function test_parse_response() { + $data_request = new Data_Request( 'GET', 'modules', 'analytics-4', 'has-property-access' ); + + $report_response = new Google_Service_AnalyticsData_RunReportResponse(); + $response = $this->datapoint->parse_response( $report_response, $data_request ); + + $this->assertSame( + $response, + $report_response, + 'The datapoint should return the response as it is, without any modifications.' + ); + } +} diff --git a/tests/phpunit/integration/Modules/Analytics_4/Datapoints/Get_Key_EventsTest.php b/tests/phpunit/integration/Modules/Analytics_4/Datapoints/Get_Key_EventsTest.php new file mode 100644 index 00000000000..e53f9b61296 --- /dev/null +++ b/tests/phpunit/integration/Modules/Analytics_4/Datapoints/Get_Key_EventsTest.php @@ -0,0 +1,147 @@ +factory()->user->create_and_get( array( 'role' => 'administrator' ) ); + $user_options = new User_Options( $context, $user->ID ); + $authentication = new Authentication( $context, $options, $user_options ); + $this->analytics = new Analytics_4( $context, $options, $user_options, $authentication ); + + $this->analytics->get_client()->withDefer( true ); + $service = new Google_Service_GoogleAnalyticsAdmin( $this->analytics->get_client() ); + + $this->datapoint = new Get_Key_Events( + array( + 'service' => function () use ( $service ) { + return $service; + }, + 'settings' => $this->analytics->get_settings(), + ), + ); + + FakeHttp::fake_google_http_handler( + $this->analytics->get_client(), + function ( Request $request ) { + $this->get_key_events_request = $request; + + $response = new GoogleAnalyticsAdminV1betaListKeyEventsResponse(); + $response->setKeyEvents( array( new GoogleAnalyticsAdminV1betaKeyEvent() ) ); + + return new FulfilledPromise( + new Response( + 200, + array(), + json_encode( $response ) + ) + ); + } + ); + + wp_set_current_user( $user->ID ); + } + + public function test_create_request__requires_property_id() { + $data_request = new Data_Request( 'GET', 'modules', 'analytics-4', 'key-events' ); + + $request = $this->datapoint->create_request( $data_request ); + + $this->assertInstanceOf( WP_Error::class, $request, 'The datapoint should return an error when the `propertyID` setting is missing.' ); + + $this->assertEquals( 'missing_required_setting', $request->get_error_code(), 'The datapoint should return a `missing_required_setting` error when the `propertyID` setting is missing.' ); + } + + public function test_create_request() { + $this->get_key_events_request = null; + + $this->analytics->get_settings()->merge( + array( + 'propertyID' => '12345', + ) + ); + + $data_request = new Data_Request( 'GET', 'modules', 'analytics-4', 'key-events' ); + + $request = $this->datapoint->create_request( $data_request ); + + $result = $this->analytics->get_client()->execute( $request ); + + $this->assertEquals( + 'https://analyticsadmin.googleapis.com/v1beta/properties/12345/keyEvents', + $this->get_key_events_request->getUri(), + 'The request should be made to the correct endpoint.' + ); + } + + public function test_parse_response() { + $data_request = new Data_Request( 'GET', 'modules', 'analytics-4', 'key-events' ); + + $key_events_response = new GoogleAnalyticsAdminV1betaListKeyEventsResponse(); + $key_events_response->setKeyEvents( array( new GoogleAnalyticsAdminV1betaKeyEvent() ) ); + + $response = $this->datapoint->parse_response( $key_events_response, $data_request ); + + $this->assertEquals( + $response, + $key_events_response->getKeyEvents(), + 'The datapoint should return an array of events.' + ); + } +} diff --git a/tests/phpunit/integration/Modules/Analytics_4/Datapoints/Get_ReportTest.php b/tests/phpunit/integration/Modules/Analytics_4/Datapoints/Get_ReportTest.php new file mode 100644 index 00000000000..3607e82886e --- /dev/null +++ b/tests/phpunit/integration/Modules/Analytics_4/Datapoints/Get_ReportTest.php @@ -0,0 +1,174 @@ +factory()->user->create_and_get( array( 'role' => 'administrator' ) ); + $user_options = new User_Options( $context, $user->ID ); + $authentication = new Authentication( $context, $options, $user_options ); + $this->analytics = new Analytics_4( $context, $options, $user_options, $authentication ); + + $this->analytics->get_client()->withDefer( true ); + $service = new Google_Service_AnalyticsData( $this->analytics->get_client() ); + + $this->report_response = new Analytics_4_Report_Response( $context ); + + $this->datapoint = new Get_Report( + array( + 'service' => function () use ( $service ) { + return $service; + }, + 'settings' => $this->analytics->get_settings(), + 'context' => $context, + 'is_shared_request' => function () { + return false; + }, + ), + ); + + FakeHttp::fake_google_http_handler( + $this->analytics->get_client(), + function ( Request $request ) { + $this->get_report_request = $request; + + $response = new Google_Service_AnalyticsData_RunReportResponse(); + + return new FulfilledPromise( new Response( 200, array(), json_encode( $response ) ) ); + } + ); + + wp_set_current_user( $user->ID ); + } + + public function test_create_request__requires_metrics_param() { + $data = array(); + + $data_request = new Data_Request( 'GET', 'modules', 'analytics-4', 'report', $data ); + + $request = $this->datapoint->create_request( $data_request ); + + $this->assertInstanceOf( WP_Error::class, $request, 'The datapoint should return an error when the `metrics` parameter is missing.' ); + + $this->assertEquals( 'missing_required_param', $request->get_error_code(), 'The datapoint should return a `missing_required_param` error when the `metrics` parameter is missing.' ); + } + + public function test_create_request__requires_property_id() { + $data = array( + 'metrics' => array( 'sessions' ), + ); + + $data_request = new Data_Request( 'GET', 'modules', 'analytics-4', 'report', $data ); + + $request = $this->datapoint->create_request( $data_request ); + + $this->assertInstanceOf( WP_Error::class, $request, 'The datapoint should return an error when the `propertyID` setting is missing.' ); + + $this->assertEquals( 'missing_required_setting', $request->get_error_code(), 'The datapoint should return a `missing_required_setting` error when the `propertyID` setting is missing.' ); + } + + public function test_create_request() { + $this->get_report_request = null; + + $this->analytics->get_settings()->merge( + array( + 'propertyID' => '12345', + ) + ); + + $data = array( + 'metrics' => array( 'sessions' ), + ); + + $data_request = new Data_Request( 'GET', 'modules', 'analytics-4', 'report', $data ); + + $request = $this->datapoint->create_request( $data_request ); + + $result = $this->analytics->get_client()->execute( $request ); + + $this->assertEquals( + 'https://analyticsdata.googleapis.com/v1beta/properties/12345:runReport', + $this->get_report_request->getUri(), + 'The request should be made to the correct endpoint.' + ); + } + + public function test_parse_response() { + $data = array( + 'metrics' => array( 'sessions' ), + ); + + $data_request = new Data_Request( 'GET', 'modules', 'analytics-4', 'report', $data ); + + $response = $this->datapoint->parse_response( new Google_Service_AnalyticsData_RunReportResponse(), $data_request ); + + $this->assertEquals( + $response, + $this->report_response->parse_response( $data_request, new Google_Service_AnalyticsData_RunReportResponse() ), + 'The datapoint should parse the response using Analytics_4_Report_Response.' + ); + } +}