connections = $connections; $this->google = $google; } /** * Registers routes. * * @return void */ public function register_routes() { $base = '/connections/(?P[a-z0-9]{2,20})'; $args = array( 'refresh' => array( 'type' => 'boolean', 'default' => false, ), ); foreach ( SourceLists::sources() as $source ) { register_rest_route( ReportController::NAMESPACE_V1, $base . '/sources/' . $source, array( 'methods' => \WP_REST_Server::READABLE, 'callback' => function ( \WP_REST_Request $request ) use ( $source ) { return $this->sources( $source, $request ); }, 'permission_callback' => array( $this, 'can_manage' ), 'args' => $args, ) ); } register_rest_route( ReportController::NAMESPACE_V1, $base . '/events', array( 'methods' => \WP_REST_Server::READABLE, 'callback' => array( $this, 'events' ), 'permission_callback' => array( $this, 'can_manage' ), 'args' => array( 'property' => array( 'type' => 'string', 'pattern' => '^properties/\d+$', 'required' => true, ), 'basis' => array( 'type' => 'string', 'enum' => array( Profile::BASIS_FIRST_USER, Profile::BASIS_SESSION ), 'default' => Profile::BASIS_FIRST_USER, ), ), ) ); } /** * Permission check. * * @return bool */ public function can_manage() { return current_user_can( Capabilities::MANAGE ); } /** * Lists Search Console sites, GA4 properties or Business Profile locations. * * @param string $source Source slug. * @param \WP_REST_Request $request Request. * @return \WP_REST_Response|\WP_Error */ private function sources( $source, \WP_REST_Request $request ) { $connection = $this->connections->find( (string) $request['connection'] ); if ( ! $connection ) { return new \WP_Error( 'turtlereport_not_found', __( 'This connection no longer exists. Reload the page and choose another connection.', 'turtlereport' ), array( 'status' => 404 ) ); } $problem = SourceLists::unavailable( $connection, $source ); if ( '' !== $problem ) { return ( new ApiException( $problem, ApiException::UNSUPPORTED ) )->to_wp_error(); } try { $items = ( new SourceLists( $this->google ) )->items( $connection, $source, $request['refresh'] ? 0 : SourceLists::TTL ); } catch ( ApiException $e ) { return $e->to_wp_error(); } return rest_ensure_response( array( 'items' => $items ) ); } /** * Events for the site editor's picker: what the GA4 property recorded from * organic search in the last 28 days, plus the events GA4 marks as key * events, including any the period didn't record. * * @param \WP_REST_Request $request Request. * @return \WP_REST_Response|\WP_Error */ public function events( \WP_REST_Request $request ) { $connection = $this->connections->find( (string) $request['connection'] ); if ( ! $connection ) { return new \WP_Error( 'turtlereport_not_found', __( 'This connection no longer exists. Reload the page and choose another connection.', 'turtlereport' ), array( 'status' => 404 ) ); } $site = new Profile(); $site->lead_basis = (string) $request['basis']; $analytics = $this->google->for_connection( $connection )->analytics(); $property = (string) $request['property']; try { $rows = $analytics->run_report( $property, array( 'dateRanges' => array( array( 'startDate' => '28daysAgo', 'endDate' => 'yesterday', ), ), 'dimensions' => array( array( 'name' => 'eventName' ) ), 'metrics' => array( array( 'name' => 'eventCount' ) ), 'dimensionFilter' => Filters::organic( $site ), 'limit' => 100, 'orderBys' => array( array( 'metric' => array( 'metricName' => 'eventCount' ), 'desc' => true, ), ), ), self::LIST_TTL ); } catch ( ApiException $e ) { return $e->to_wp_error(); } // Key events are a bonus: if the Admin API won't answer, the recorded list still works on its own. try { $key_events = $analytics->key_events( $property, self::LIST_TTL ); } catch ( ApiException $e ) { $key_events = array(); } return rest_ensure_response( EventCatalog::build( array_map( static function ( $row ) { return array( 'name' => $row['d'][0], 'count' => (int) $row['m'][0], ); }, $rows ), $key_events ) ); } }