http = $http; } /** * Verified sites the account can read. * * @param int $ttl Cache lifetime. * @return string[] Site URLs, e.g. "sc-domain:example.com". * @throws ApiException On failure. */ public function sites( $ttl ) { $data = $this->http->get( self::BASE . '/sites', $ttl ); $sites = array(); foreach ( isset( $data['siteEntry'] ) ? (array) $data['siteEntry'] : array() as $entry ) { if ( ! empty( $entry['siteUrl'] ) && ( ! isset( $entry['permissionLevel'] ) || 'siteUnverifiedUser' !== $entry['permissionLevel'] ) ) { $sites[] = (string) $entry['siteUrl']; } } sort( $sites ); return $sites; } /** * Runs a search analytics query for one dimension. * * Uses final data only, so numbers don't shift between visits. * * Breakdowns ask for as many rows as the limit allows in one request and * only ask again when Google filled that request completely, so an ordinary * site costs exactly one call while a large one keeps going until its * keywords or pages run out. Totals are left alone: without a dimension * Google answers with a single row, and there is nothing to page through. * * @param string $site Site URL. * @param string $dimension date, page, query, country or device; empty for totals. * @param string $start Start date (Y-m-d). * @param string $end End date (Y-m-d). * @param string $type Search type: web, image, video or news. * @param int $ttl Cache lifetime. * @param array $filters Optional dimensionFilterGroups. * @param int $limit Most rows to collect; 0 uses the default. * @return array[] Rows with keys, clicks, impressions, ctr and position. * @throws ApiException On failure. */ public function query( $site, $dimension, $start, $end, $type, $ttl, array $filters = array(), $limit = 0 ) { $body = array( 'startDate' => $start, 'endDate' => $end, 'dimensions' => '' === $dimension ? array() : array( $dimension ), 'type' => $type, 'rowLimit' => 1000, 'dataState' => 'final', ); if ( $filters ) { $body['dimensionFilterGroups'] = $filters; } $url = self::BASE . '/sites/' . rawurlencode( $site ) . '/searchAnalytics/query'; if ( '' === $dimension ) { $data = $this->http->post( $url, $body, $ttl ); return isset( $data['rows'] ) ? array_values( (array) $data['rows'] ) : array(); } $limit = self::limit( $dimension, $limit ); $rows = array(); for ( $page = 0; $page < self::MAX_PAGES; $page++ ) { $want = min( self::PAGE_ROWS, $limit - count( $rows ) ); $body['rowLimit'] = $want; $body['startRow'] = count( $rows ); $data = $this->http->post( $url, $body, $ttl ); $batch = isset( $data['rows'] ) ? array_values( (array) $data['rows'] ) : array(); $rows = array_merge( $rows, $batch ); if ( count( $batch ) < $want || count( $rows ) >= $limit ) { break; } } return $rows; } /** * How many rows one breakdown may collect. * * @param string $dimension Dimension being broken down. * @param int $limit Caller's limit; 0 uses the default. * @return int */ private static function limit( $dimension, $limit ) { $limit = (int) $limit > 0 ? (int) $limit : self::ROW_LIMIT; /** * Filters how many rows one Search Console breakdown collects. * * Raising this gives large sites more of their keywords and pages, at * one extra request per 25,000 rows. * * @param int $limit Row limit. * @param string $dimension Dimension being broken down. */ $limit = (int) apply_filters( 'turtlereport_gsc_row_limit', $limit, $dimension ); return max( 1, min( $limit, self::PAGE_ROWS * self::MAX_PAGES ) ); } /** * Totals for a date range, optionally filtered. * * @param string $site Site URL. * @param string $start Start date. * @param string $end End date. * @param string $type Search type. * @param int $ttl Cache lifetime. * @param array $filters Optional dimensionFilterGroups. * @return array{clicks:float,impressions:float,ctr:float,position:float} * @throws ApiException On failure. */ public function totals( $site, $start, $end, $type, $ttl, array $filters = array() ) { $rows = $this->query( $site, '', $start, $end, $type, $ttl, $filters ); $row = isset( $rows[0] ) ? $rows[0] : array(); return array( 'clicks' => isset( $row['clicks'] ) ? (float) $row['clicks'] : 0.0, 'impressions' => isset( $row['impressions'] ) ? (float) $row['impressions'] : 0.0, 'ctr' => isset( $row['ctr'] ) ? (float) $row['ctr'] : 0.0, 'position' => isset( $row['position'] ) ? (float) $row['position'] : 0.0, ); } }