'string', 'pattern' => '^\d{4}-\d{2}-\d{2}$', 'required' => true, ); return array( 'prev_start' => $date, 'prev_end' => $date, ); } /** * {@inheritDoc} * * @param Profile $site Site. * @param array $params Parameters. * @return array */ public function run( Profile $site, array $params ) { $this->site = $site; $this->ga = $this->google->for_site( $site, Profile::SOURCE_ANALYTICS )->analytics(); $this->cache_ttl = $this->ttl( $params['end'] ); $segment = $this->segment( $site, $params ); $this->segment_filter = $segment ? SegmentFilter::ga( $segment ) : null; $events = $site->all_conversion_events(); $s = $params['start']; $e = $params['end']; $mode = 'channel'; $cur = $this->totals( $mode, $s, $e ); if ( 0 === $cur['sessions'] ) { $alt = $this->totals( 'source', $s, $e ); if ( $alt['sessions'] > 0 ) { $mode = 'source'; $cur = $alt; } } return array( 'mode' => $mode, 'cur' => $cur, 'prev' => $this->totals( $mode, $params['prev_start'], $params['prev_end'] ), 'ev' => self::map( $this->event_counts( $mode, $events, $s, $e ) ), 'pev' => self::map( $this->event_counts( $mode, $events, $params['prev_start'], $params['prev_end'] ) ), 'pages' => $this->top_pages( $mode, $s, $e ), 'page_leads' => self::map( $this->page_leads( $mode, $events, $s, $e ) ), 'sources' => $this->sources( $mode, $s, $e ), 'all' => $this->totals( null, $s, $e )['sessions'], ); } /** * Filter for AI traffic. * * @param string|null $mode channel, source, or null for all traffic. * @return array|null */ private function ai_filter( $mode ) { return Filters::all( $this->base_filter( $mode ), $this->segment_filter ); } /** * AI channel or source filter, before segments. * * @param string|null $mode channel, source, or null for all traffic. * @return array|null */ private function base_filter( $mode ) { if ( null === $mode ) { return null; } if ( 'source' === $mode ) { /** * Filters the pattern used to spot AI assistant sources when GA4 * has no "AI Assistant" channel for a property. * * @param string $pattern RE2 regular expression. */ return Filters::regex( 'sessionSource', (string) apply_filters( 'turtlereport_ai_source_pattern', self::SOURCE_PATTERN ) ); } return Filters::exact( 'sessionDefaultChannelGroup', 'AI Assistant' ); } /** * Sessions and engaged sessions. * * @param string|null $mode Filter mode. * @param string $start Start date. * @param string $end End date. * @return array */ private function totals( $mode, $start, $end ) { $body = array( 'dateRanges' => array( array( 'startDate' => $start, 'endDate' => $end, ), ), 'metrics' => array( array( 'name' => 'sessions' ), array( 'name' => 'engagedSessions' ) ), ); $filter = $this->ai_filter( $mode ); if ( $filter ) { $body['dimensionFilter'] = $filter; } $rows = $this->ga->run_report( $this->site->ga_property, $body, $this->cache_ttl ); return array( 'sessions' => isset( $rows[0] ) ? (int) $rows[0]['m'][0] : 0, 'engaged' => isset( $rows[0] ) ? (int) $rows[0]['m'][1] : 0, ); } /** * Lead event counts from AI traffic. * * @param string $mode Filter mode. * @param string[] $events Lead events. * @param string $start Start date. * @param string $end End date. * @return array */ private function event_counts( $mode, array $events, $start, $end ) { if ( ! $events ) { return array(); } $rows = $this->ga->run_report( $this->site->ga_property, array( 'dateRanges' => array( array( 'startDate' => $start, 'endDate' => $end, ), ), 'dimensions' => array( array( 'name' => 'eventName' ) ), 'metrics' => array( array( 'name' => 'eventCount' ) ), 'dimensionFilter' => Filters::all( $this->ai_filter( $mode ), Filters::events( $events ) ), ), $this->cache_ttl ); $out = array(); foreach ( $rows as $row ) { $out[ $row['d'][0] ] = (int) $row['m'][0]; } return $out; } /** * Top landing pages for AI traffic. * * @param string $mode Filter mode. * @param string $start Start date. * @param string $end End date. * @return array[] */ private function top_pages( $mode, $start, $end ) { $rows = $this->ga->run_report( $this->site->ga_property, array( 'dateRanges' => array( array( 'startDate' => $start, 'endDate' => $end, ), ), 'dimensions' => array( array( 'name' => 'landingPage' ) ), 'metrics' => array( array( 'name' => 'sessions' ) ), 'dimensionFilter' => $this->ai_filter( $mode ), 'orderBys' => array( array( 'metric' => array( 'metricName' => 'sessions' ), 'desc' => true, ), ), 'limit' => 10, ), $this->cache_ttl ); $out = array(); foreach ( $rows as $row ) { if ( '' !== $row['d'][0] && '(not set)' !== $row['d'][0] ) { $out[] = array( 'p' => $row['d'][0], 'sessions' => (int) $row['m'][0], ); } } return $out; } /** * Lead events per landing page from AI traffic. * * @param string $mode Filter mode. * @param string[] $events Lead events. * @param string $start Start date. * @param string $end End date. * @return array */ private function page_leads( $mode, array $events, $start, $end ) { if ( ! $events ) { return array(); } $rows = $this->ga->run_report( $this->site->ga_property, array( 'dateRanges' => array( array( 'startDate' => $start, 'endDate' => $end, ), ), 'dimensions' => array( array( 'name' => 'landingPage' ), array( 'name' => 'eventName' ) ), 'metrics' => array( array( 'name' => 'eventCount' ) ), 'dimensionFilter' => Filters::all( $this->ai_filter( $mode ), Filters::events( $events ) ), 'limit' => 1000, ), $this->cache_ttl ); $out = array(); foreach ( $rows as $row ) { $path = self::normalize_path( $row['d'][0] ); $event = $row['d'][1]; $out[ $path ][ $event ] = ( isset( $out[ $path ][ $event ] ) ? $out[ $path ][ $event ] : 0 ) + (int) $row['m'][0]; } return $out; } /** * Top AI sources. * * @param string $mode Filter mode. * @param string $start Start date. * @param string $end End date. * @return array[] */ private function sources( $mode, $start, $end ) { $rows = $this->ga->run_report( $this->site->ga_property, array( 'dateRanges' => array( array( 'startDate' => $start, 'endDate' => $end, ), ), 'dimensions' => array( array( 'name' => 'sessionSource' ) ), 'metrics' => array( array( 'name' => 'sessions' ) ), 'dimensionFilter' => $this->ai_filter( $mode ), 'orderBys' => array( array( 'metric' => array( 'metricName' => 'sessions' ), 'desc' => true, ), ), 'limit' => 8, ), $this->cache_ttl ); return array_map( static function ( $row ) { return array( 'name' => $row['d'][0], 'sessions' => (int) $row['m'][0], ); }, $rows ); } }