true, Middleware::class => true, Merchant::class => true, MerchantMetrics::class => true, Ads::class => true, AdsAssetGroup::class => true, AdsCampaign::class => true, AdsCampaignBudget::class => true, AdsConversionAction::class => true, AdsReport::class => true, AdsAssetGroupAsset::class => true, AdsAsset::class => true, 'connect_server_root' => true, Connection::class => true, GoogleProductService::class => true, GooglePromotionService::class => true, SiteVerification::class => true, Settings::class => true, ]; /** * Use the register method to register items with the container via the * protected $this->leagueContainer property or the `getLeagueContainer` method * from the ContainerAwareTrait. * * @return void */ public function register() { $this->register_guzzle(); $this->register_ads_client(); $this->register_google_classes(); $this->share( Middleware::class, ContainerInterface::class ); $this->add( Connection::class ); $this->add( Settings::class, ContainerInterface::class ); $this->share( Ads::class, GoogleAdsClient::class ); $this->share( AdsAssetGroup::class, GoogleAdsClient::class, AdsAssetGroupAsset::class ); $this->share( AdsCampaign::class, GoogleAdsClient::class, AdsCampaignBudget::class, AdsCampaignCriterion::class, GoogleHelper::class ); $this->share( AdsCampaignBudget::class, GoogleAdsClient::class ); $this->share( AdsAssetGroupAsset::class, GoogleAdsClient::class, AdsAsset::class ); $this->share( AdsAsset::class, GoogleAdsClient::class, WP::class ); $this->share( AdsCampaignCriterion::class ); $this->share( AdsConversionAction::class, GoogleAdsClient::class ); $this->share( AdsReport::class, GoogleAdsClient::class ); $this->share( Merchant::class, ShoppingContent::class ); $this->share( MerchantMetrics::class, ShoppingContent::class, GoogleAdsClient::class, WP::class, TransientsInterface::class ); $this->share( MerchantReport::class, ShoppingContent::class, ProductHelper::class ); $this->share( SiteVerification::class ); $this->getLeagueContainer()->add( 'connect_server_root', $this->get_connect_server_url_root() ); } /** * Register guzzle with authorization middleware added. */ protected function register_guzzle() { $callback = function () { $handler_stack = HandlerStack::create(); $handler_stack->remove( 'http_errors' ); $handler_stack->push( $this->error_handler(), 'http_errors' ); $handler_stack->push( $this->add_auth_header(), 'auth_header' ); $handler_stack->push( $this->add_plugin_version_header(), 'plugin_version_header' ); // Override endpoint URL if we are using http locally. if ( 0 === strpos( $this->get_connect_server_url_root()->getValue(), 'http://' ) ) { $handler_stack->push( $this->override_http_url(), 'override_http_url' ); } return new GuzzleClient( [ 'handler' => $handler_stack ] ); }; $this->share_concrete( GuzzleClient::class, new Definition( GuzzleClient::class, $callback ) ); $this->share_concrete( ClientInterface::class, new Definition( GuzzleClient::class, $callback ) ); } /** * Register ads client. */ protected function register_ads_client() { $callback = function () { return new GoogleAdsClient( $this->get_connect_server_endpoint() ); }; $this->share_concrete( GoogleAdsClient::class, new Definition( GoogleAdsClient::class, $callback ) )->addMethodCall( 'setHttpClient', [ ClientInterface::class ] ); } /** * Register the various Google classes we use. */ protected function register_google_classes() { $this->add( Client::class )->addMethodCall( 'setHttpClient', [ ClientInterface::class ] ); $this->add( ShoppingContent::class, Client::class, $this->get_connect_server_url_root( 'google/google-mc' ) ); $this->add( SiteVerificationService::class, Client::class, $this->get_connect_server_url_root( 'google/google-sv' ) ); $this->share( GoogleProductService::class, ShoppingContent::class ); $this->share( GooglePromotionService::class, ShoppingContent::class ); } /** * Custom error handler to detect and handle a disconnected status. * * @return callable */ protected function error_handler(): callable { return function ( callable $handler ) { return function ( RequestInterface $request, array $options ) use ( $handler ) { return $handler( $request, $options )->then( function ( ResponseInterface $response ) use ( $request ) { $code = $response->getStatusCode(); $path = $request->getUri()->getPath(); // Partial Failures come back with a status code of 200, so it's necessary to call GoogleAdsFailures:init every time. if ( strpos( $path, 'google-ads' ) !== false ) { GoogleAdsFailures::init(); } if ( $code < 400 ) { return $response; } if ( 401 === $code ) { $this->handle_unauthorized_error( $request, $response ); } throw RequestException::create( $request, $response ); } ); }; }; } /** * Handle a 401 unauthorized error. * Marks either the Jetpack or the Google account as disconnected. * * @since 1.12.5 * * @param RequestInterface $request * @param ResponseInterface $response * * @throws AccountReconnect When an account must be reconnected. */ protected function handle_unauthorized_error( RequestInterface $request, ResponseInterface $response ) { $auth_header = $response->getHeader( 'www-authenticate' )[0] ?? ''; if ( 0 === strpos( $auth_header, 'X_JP_Auth' ) ) { // Log original exception before throwing reconnect exception. do_action( 'woocommerce_gla_exception', RequestException::create( $request, $response ), __METHOD__ ); $this->set_jetpack_connected( false ); throw AccountReconnect::jetpack_disconnected(); } // Exclude listing customers as it will handle it's own unauthorized errors. $path = $request->getUri()->getPath(); if ( false === strpos( $path, 'customers:listAccessibleCustomers' ) ) { // Log original exception before throwing reconnect exception. do_action( 'woocommerce_gla_exception', RequestException::create( $request, $response ), __METHOD__ ); $this->set_google_disconnected(); throw AccountReconnect::google_disconnected(); } } /** * @return callable */ protected function add_auth_header(): callable { return function ( callable $handler ) { return function ( RequestInterface $request, array $options ) use ( $handler ) { try { $request = $request->withHeader( 'Authorization', $this->generate_auth_header() ); // Getting a valid authorization token, indicates Jetpack is connected. $this->set_jetpack_connected( true ); } catch ( WPError $error ) { do_action( 'woocommerce_gla_guzzle_client_exception', $error, __METHOD__ . ' in add_auth_header()' ); $this->set_jetpack_connected( false ); throw AccountReconnect::jetpack_disconnected(); } return $handler( $request, $options ); }; }; } /** * Add client name and version headers to request * * @since 2.4.11 * * @return callable */ protected function add_plugin_version_header(): callable { return function ( callable $handler ) { return function ( RequestInterface $request, array $options ) use ( $handler ) { $request = $request->withHeader( 'x-client-name', $this->get_client_name() ) ->withHeader( 'x-client-version', $this->get_version() ); return $handler( $request, $options ); }; }; } /** * @return callable */ protected function override_http_url(): callable { return function ( callable $handler ) { return function ( RequestInterface $request, array $options ) use ( $handler ) { $request = $request->withUri( $request->getUri()->withScheme( 'http' ) ); return $handler( $request, $options ); }; }; } /** * Generate the authorization header for the GuzzleClient and GoogleAdsClient. * * @return string Empty if no access token is available. * * @throws WPError If the authorization token isn't found. */ protected function generate_auth_header(): string { /** @var Manager $manager */ $manager = $this->getLeagueContainer()->get( Manager::class ); $token = $manager->get_tokens()->get_access_token( false, false, false ); $this->check_for_wp_error( $token ); [ $key, $secret ] = explode( '.', $token->secret ); $key = sprintf( '%s:%d:%d', $key, defined( 'JETPACK__API_VERSION' ) ? JETPACK__API_VERSION : 1, $token->external_user_id ); $timestamp = time() + (int) Jetpack_Options::get_option( 'time_diff' ); $nonce = wp_generate_password( 10, false ); $request = join( "\n", [ $key, $timestamp, $nonce, '' ] ); $signature = base64_encode( hash_hmac( 'sha1', $request, $secret, true ) ); $auth = [ 'token' => $key, 'timestamp' => $timestamp, 'nonce' => $nonce, 'signature' => $signature, ]; $pieces = [ 'X_JP_Auth' ]; foreach ( $auth as $key => $value ) { $pieces[] = sprintf( '%s="%s"', $key, $value ); } return join( ' ', $pieces ); } /** * Get the root Url for the connect server. * * @param string $path (Optional) A path relative to the root to include. * * @return RawArgument */ protected function get_connect_server_url_root( string $path = '' ): RawArgument { $url = trailingslashit( $this->get_connect_server_url() ); $path = trim( $path, '/' ); return new RawArgument( "{$url}{$path}" ); } /** * Get the connect server endpoint in the format `domain:port/path` * * @return string */ protected function get_connect_server_endpoint(): string { $parts = wp_parse_url( $this->get_connect_server_url_root( 'google/google-ads' )->getValue() ); $port = empty( $parts['port'] ) ? 443 : $parts['port']; return sprintf( '%s:%d%s', $parts['host'], $port, $parts['path'] ); } /** * Set the Google account connection as disconnected. */ protected function set_google_disconnected() { /** @var Options $options */ $options = $this->getLeagueContainer()->get( OptionsInterface::class ); $options->update( OptionsInterface::GOOGLE_CONNECTED, false ); } /** * Set the Jetpack account connection. * * @since 1.12.5 * * @param bool $connected Is connected or disconnected? */ protected function set_jetpack_connected( bool $connected ) { /** @var Options $options */ $options = $this->getLeagueContainer()->get( OptionsInterface::class ); // Save previous connected status before updating. $previous_connected = boolval( $options->get( OptionsInterface::JETPACK_CONNECTED ) ); $options->update( OptionsInterface::JETPACK_CONNECTED, $connected ); if ( $previous_connected !== $connected ) { $this->jetpack_connected_change( $connected ); } } /** * Handle the reconnect notification when the Jetpack connection status changes. * * @since 1.12.5 * * @param boolean $connected */ protected function jetpack_connected_change( bool $connected ) { /** @var ReconnectWordPress $note */ $note = $this->getLeagueContainer()->get( ReconnectWordPress::class ); if ( $connected ) { $note->delete(); } else { $note->get_entry()->save(); } } }
Fatal error: Uncaught Automattic\WooCommerce\GoogleListingsAndAds\Vendor\League\Container\Exception\ContainerException: A service provider must be a fully qualified class name or instance of (\League\Container\ServiceProvider\ServiceProviderInterface) in /htdocs/wp-content/plugins/google-listings-and-ads/vendor/league/container/src/ServiceProvider/ServiceProviderAggregate.php:52 Stack trace: #0 /htdocs/wp-content/plugins/google-listings-and-ads/vendor/league/container/src/Container.php(146): Automattic\WooCommerce\GoogleListingsAndAds\Vendor\League\Container\ServiceProvider\ServiceProviderAggregate->add('Automattic\\WooC...') #1 /htdocs/wp-content/plugins/google-listings-and-ads/src/Container.php(75): Automattic\WooCommerce\GoogleListingsAndAds\Vendor\League\Container\Container->addServiceProvider('Automattic\\WooC...') #2 /htdocs/wp-content/plugins/google-listings-and-ads/google-listings-and-ads.php(93): Automattic\WooCommerce\GoogleListingsAndAds\Container->__construct() #3 /htdocs/wp-content/plugins/google-listings-and-ads/src/PluginFactory.php(25): woogle_get_container() #4 /htdocs/wp-content/plugins/google-listings-and-ads/google-listings-and-ads.php(72): Automattic\WooCommerce\GoogleListingsAndAds\PluginFactory::instance() #5 /htdocs/wp-includes/class-wp-hook.php(324): {closure}('') #6 /htdocs/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters('', Array) #7 /htdocs/wp-includes/plugin.php(517): WP_Hook->do_action(Array) #8 /htdocs/wp-content/plugins/woocommerce/includes/class-woocommerce.php(218): do_action('woocommerce_loa...') #9 /htdocs/wp-includes/class-wp-hook.php(324): WooCommerce->on_plugins_loaded('') #10 /htdocs/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters(NULL, Array) #11 /htdocs/wp-includes/plugin.php(517): WP_Hook->do_action(Array) #12 /htdocs/wp-settings.php(559): do_action('plugins_loaded') #13 /htdocs/wp-config.php(85): require_once('/htdocs/wp-sett...') #14 /htdocs/wp-load.php(50): require_once('/htdocs/wp-conf...') #15 /htdocs/wp-blog-header.php(13): require_once('/htdocs/wp-load...') #16 /htdocs/index.php(17): require('/htdocs/wp-blog...') #17 {main} thrown in /htdocs/wp-content/plugins/google-listings-and-ads/vendor/league/container/src/ServiceProvider/ServiceProviderAggregate.php on line 52