connections = $connections; $this->credentials = $credentials; $this->cache = $cache; } /** * Services for reading one site's report, cached in that site's group. * * Each source (Search Console, GA4, Business Profile) can read through its * own connection; without a source, the site's main connection is used, * which is what reports added through the turtlereport_reports filter get. * Cache keys include the credential ID, so two connections never share * cached answers even inside one site's group. * * @param Profile $site Site. * @param string|null $source Profile::SOURCE_* constant, or null for the main connection. * @return Services * @throws ApiException When the site has no working connection for that source. */ public function for_site( Profile $site, $source = null ) { $id = null === $source ? $site->connection_id : $site->connection_for( (string) $source ); $connection = '' !== $id ? $this->connections->find( $id ) : null; if ( ! $connection ) { throw new ApiException( self::missing_message( $source ), ApiException::NOT_CONFIGURED ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Caught by the plugin; escaped where the message is shown. } return $this->build( $connection, self::site_group( $site->id ) ); } /** * The connection a site's source reads through, if it exists. * * @param Profile $site Site. * @param string|null $source Profile::SOURCE_* constant, or null for the main connection. * @return Connection|null */ public function connection_for( Profile $site, $source = null ) { $id = null === $source ? $site->connection_id : $site->connection_for( (string) $source ); return '' !== $id ? $this->connections->find( $id ) : null; } /** * Message for a source with no connection. * * @param string|null $source Source. * @return string */ private static function missing_message( $source ) { switch ( $source ) { case Profile::SOURCE_SEARCH: return __( 'Search Console has no Google connection for this site, or its connection was deleted. An administrator needs to choose one in the site\'s settings.', 'turtlereport' ); case Profile::SOURCE_ANALYTICS: return __( 'Google Analytics has no Google connection for this site, or its connection was deleted. An administrator needs to choose one in the site\'s settings.', 'turtlereport' ); case Profile::SOURCE_BUSINESS: return __( 'Business Profile has no Google connection for this site, or its connection was deleted. An administrator needs to choose one in the site\'s settings.', 'turtlereport' ); } return __( 'This site has no Google connection. An administrator needs to choose one in the site\'s settings.', 'turtlereport' ); } /** * Services for setup screens, cached in the connection's group. * * @param Connection $connection Connection. * @return Services * @throws ApiException When the connection isn't usable. */ public function for_connection( Connection $connection ) { return $this->build( $connection, self::connection_group( $connection->id ) ); } /** * Cache group for a site. * * @param string $site_id Site ID. * @return string */ public static function site_group( $site_id ) { return 'site:' . $site_id; } /** * Cache group for a connection. * * @param string $connection_id Connection ID. * @return string */ public static function connection_group( $connection_id ) { return 'connection:' . $connection_id; } /** * Builds services. * * @param Connection $connection Connection. * @param string $group Cache group. * @return Services */ private function build( Connection $connection, $group ) { $credentials = $this->credentials->for_connection( $connection ); return new Services( $credentials, new HttpClient( $credentials, $this->cache, $group ) ); } }