8889841cTag_Manager/Web_Tag.php                                                                             0000644                 00000006037 15051555666 0010756 0                                                                                                    ustar 00                                                                                                                                                                                                                                                       get_method_proxy_once( 'render_no_js' );
		add_action( 'wp_head', $this->get_method_proxy( 'render' ) );
		// For non-AMP (if `wp_body_open` supported).
		add_action( 'wp_body_open', $render_no_js, -9999 );
		// For non-AMP (as fallback).
		add_action( 'wp_footer', $render_no_js );
		add_filter(
			'wp_resource_hints',
			$this->get_dns_prefetch_hints_callback( '//www.googletagmanager.com' ),
			10,
			2
		);
		$this->do_init_tag_action();
	}
	/**
	 * Outputs Tag Manager script.
	 *
	 * @since 1.24.0
	 */
	protected function render() {
		$tag_manager_inline_script = sprintf(
			"
			( function( w, d, s, l, i ) {
				w[l] = w[l] || [];
				w[l].push( {'gtm.start': new Date().getTime(), event: 'gtm.js'} );
				var f = d.getElementsByTagName( s )[0],
					j = d.createElement( s ), dl = l != 'dataLayer' ? '&l=' + l : '';
				j.async = true;
				j.src = 'https://www.googletagmanager.com/gtm.js?id=' + i + dl;
				f.parentNode.insertBefore( j, f );
			} )( window, document, 'script', 'dataLayer', '%s' );
			",
			esc_js( $this->tag_id )
		);
		$tag_manager_consent_attribute = $this->get_tag_blocked_on_consent_attribute_array();
		printf( "\n\n", esc_html__( 'Google Tag Manager snippet added by Site Kit', 'google-site-kit' ) );
		BC_Functions::wp_print_inline_script_tag( $tag_manager_inline_script, $tag_manager_consent_attribute );
		printf( "\n\n", esc_html__( 'End Google Tag Manager snippet added by Site Kit', 'google-site-kit' ) );
	}
	/**
	 * Outputs Tag Manager iframe for when the browser has JavaScript disabled.
	 *
	 * @since 1.24.0
	 */
	private function render_no_js() {
		// Consent-based blocking requires JS to be enabled so we need to bail here if present.
		if ( $this->get_tag_blocked_on_consent_attribute() ) {
			return;
		}
		$iframe_src = 'https://www.googletagmanager.com/ns.html?id=' . rawurlencode( $this->tag_id );
		?>
		
		
		
		register_legacy_keys_migration(
			array(
				'account_id'   => 'accountID',
				'accountId'    => 'accountID',
				'container_id' => 'containerID',
				'containerId'  => 'containerID',
			)
		);
		$this->register_owned_keys();
	}
	/**
	 * Returns keys for owned settings.
	 *
	 * @since 1.16.0
	 *
	 * @return array An array of keys for owned settings.
	 */
	public function get_owned_keys() {
		return array(
			'accountID',
			'ampContainerID',
			'containerID',
			'internalAMPContainerID',
			'internalContainerID',
		);
	}
	/**
	 * Gets the default value.
	 *
	 * @since 1.2.0
	 *
	 * @return array
	 */
	protected function get_default() {
		return array(
			'ownerID'                => 0,
			'accountID'              => '',
			'ampContainerID'         => '',
			'containerID'            => '',
			'internalContainerID'    => '',
			'internalAMPContainerID' => '',
			'useSnippet'             => true,
			'gaPropertyID'           => '',
		);
	}
	/**
	 * Gets the callback for sanitizing the setting's value before saving.
	 *
	 * @since 1.6.0
	 *
	 * @return callable|null
	 */
	protected function get_sanitize_callback() {
		return function( $option ) {
			if ( is_array( $option ) ) {
				if ( isset( $option['useSnippet'] ) ) {
					$option['useSnippet'] = (bool) $option['useSnippet'];
				}
			}
			return $option;
		};
	}
}
                                                                                                                                                                                                                        Tag_Manager/AMP_Tag.php                                                                             0000644                 00000005004 15051555666 0010647 0                                                                                                    ustar 00                                                                                                                                                                                                                                                       get_method_proxy_once( 'render' );
		// Which actions are run depends on the version of the AMP Plugin
		// (https://amp-wp.org/) available. Version >=1.3 exposes a
		// new, `amp_print_analytics` action.
		// For all AMP modes, AMP plugin version >=1.3.
		add_action( 'amp_print_analytics', $render );
		// For AMP Standard and Transitional, AMP plugin version <1.3.
		add_action( 'wp_footer', $render, 20 );
		// For AMP Reader, AMP plugin version <1.3.
		add_action( 'amp_post_template_footer', $render, 20 );
		// For Web Stories plugin.
		add_action( 'web_stories_print_analytics', $render );
		// Load amp-analytics component for AMP Reader.
		$this->enqueue_amp_reader_component_script( 'amp-analytics', 'https://cdn.ampproject.org/v0/amp-analytics-0.1.js' );
		$this->do_init_tag_action();
	}
	/**
	 * Outputs Tag Manager  tag.
	 *
	 * @since 1.24.0
	 */
	protected function render() {
		// Add the optoutElementId for compatibility with our Analytics opt-out mechanism.
		// This configuration object will be merged with the configuration object returned
		// by the `config` attribute URL.
		$gtm_amp_opt = array(
			'optoutElementId' => '__gaOptOutExtension',
		);
		printf( "\n\n", esc_html__( 'Google Tag Manager AMP snippet added by Site Kit', 'google-site-kit' ) );
		printf(
			'',
			esc_url( 'https://www.googletagmanager.com/amp.json?id=' . rawurlencode( $this->tag_id ) ), // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
			$this->get_tag_blocked_on_consent_attribute(), // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
			wp_json_encode( $gtm_amp_opt )
		);
		printf( "\n\n", esc_html__( 'End Google Tag Manager AMP snippet added by Site Kit', 'google-site-kit' ) );
	}
}
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            Tag_Manager/Tag_Guard.php                                                                           0000644                 00000002560 15051555666 0011300 0                                                                                                    ustar 00                                                                                                                                                                                                                                                       is_amp = $is_amp;
	}
	/**
	 * Determines whether the guarded tag can be activated or not.
	 *
	 * @since 1.24.0
	 *
	 * @return bool|WP_Error TRUE if guarded tag can be activated, otherwise FALSE or an error.
	 */
	public function can_activate() {
		$settings     = $this->settings->get();
		$container_id = $this->is_amp ? $settings['ampContainerID'] : $settings['containerID'];
		return ! empty( $settings['useSnippet'] ) && ! empty( $container_id );
	}
}
                                                                                                                                                AdSense.php                                                                                         0000644                 00000072716 15051555666 0006632 0                                                                                                    ustar 00                                                                                                                                                                                                                                                       register_scopes_hook();
		add_action( 'wp_head', $this->get_method_proxy_once( 'render_platform_meta_tags' ) );
		if ( $this->is_connected() ) {
			/**
			 * Release filter forcing unlinked state.
			 *
			 * This is hooked into 'init' (default priority of 10), so that it
			 * runs after the original filter is added.
			 *
			 * @see \Google\Site_Kit\Modules\Analytics::register()
			 * @see \Google\Site_Kit\Modules\Analytics\Settings::register()
			 */
			add_action(
				'googlesitekit_init',
				function () {
					remove_filter( 'googlesitekit_analytics_adsense_linked', '__return_false' );
				}
			);
		}
		// AdSense tag placement logic.
		add_action( 'template_redirect', $this->get_method_proxy( 'register_tag' ) );
	}
	/**
	 * Gets required Google OAuth scopes for the module.
	 *
	 * @since 1.0.0
	 * @since 1.9.0 Changed to `adsense.readonly` variant.
	 *
	 * @return array List of Google OAuth scopes.
	 */
	public function get_scopes() {
		return array(
			'https://www.googleapis.com/auth/adsense.readonly',
		);
	}
	/**
	 * Checks whether the module is connected.
	 *
	 * A module being connected means that all steps required as part of its activation are completed.
	 *
	 * @since 1.0.0
	 *
	 * @return bool True if module is connected, false otherwise.
	 */
	public function is_connected() {
		$settings = $this->get_settings()->get();
		if ( empty( $settings['accountSetupComplete'] ) || empty( $settings['siteSetupComplete'] ) ) {
			return false;
		}
		return parent::is_connected();
	}
	/**
	 * Cleans up when the module is deactivated.
	 *
	 * @since 1.0.0
	 */
	public function on_deactivation() {
		$this->get_settings()->delete();
	}
	/**
	 * Gets an array of debug field definitions.
	 *
	 * @since 1.5.0
	 *
	 * @return array
	 */
	public function get_debug_fields() {
		$settings = $this->get_settings()->get();
		return array(
			'adsense_account_id'            => array(
				'label' => __( 'AdSense account ID', 'google-site-kit' ),
				'value' => $settings['accountID'],
				'debug' => Debug_Data::redact_debug_value( $settings['accountID'], 7 ),
			),
			'adsense_client_id'             => array(
				'label' => __( 'AdSense client ID', 'google-site-kit' ),
				'value' => $settings['clientID'],
				'debug' => Debug_Data::redact_debug_value( $settings['clientID'], 10 ),
			),
			'adsense_account_status'        => array(
				'label' => __( 'AdSense account status', 'google-site-kit' ),
				'value' => $settings['accountStatus'],
			),
			'adsense_site_status'           => array(
				'label' => __( 'AdSense site status', 'google-site-kit' ),
				'value' => $settings['siteStatus'],
			),
			'adsense_use_snippet'           => array(
				'label' => __( 'AdSense snippet placed', 'google-site-kit' ),
				'value' => $settings['useSnippet'] ? __( 'Yes', 'google-site-kit' ) : __( 'No', 'google-site-kit' ),
				'debug' => $settings['useSnippet'] ? 'yes' : 'no',
			),
			'adsense_web_stories_adunit_id' => array(
				'label' => __( 'Web Stories Ad Unit ID', 'google-site-kit' ),
				'value' => $settings['webStoriesAdUnit'],
				'debug' => $settings['webStoriesAdUnit'],
			),
		);
	}
	/**
	 * Gets map of datapoint to definition data for each.
	 *
	 * @since 1.12.0
	 *
	 * @return array Map of datapoints to their definitions.
	 */
	protected function get_datapoint_definitions() {
		return array(
			'GET:adunits'       => array( 'service' => 'adsense' ),
			'GET:accounts'      => array( 'service' => 'adsense' ),
			'GET:alerts'        => array( 'service' => 'adsense' ),
			'GET:clients'       => array( 'service' => 'adsense' ),
			'GET:report'        => array(
				'service'   => 'adsense',
				'shareable' => Feature_Flags::enabled( 'dashboardSharing' ),
			),
			'GET:notifications' => array( 'service' => '' ),
			'GET:urlchannels'   => array( 'service' => 'adsense' ),
			'GET:sites'         => array( 'service' => 'adsense' ),
		);
	}
	/**
	 * Creates a request object for the given datapoint.
	 *
	 * @since 1.0.0
	 *
	 * @param Data_Request $data Data request object.
	 * @return RequestInterface|callable|WP_Error Request object or callable on success, or WP_Error on failure.
	 *
	 * @throws Invalid_Datapoint_Exception Thrown if the datapoint does not exist.
	 */
	protected function create_data_request( Data_Request $data ) {
		switch ( "{$data->method}:{$data->datapoint}" ) {
			case 'GET:accounts':
				$service = $this->get_service( 'adsense' );
				return $service->accounts->listAccounts();
			case 'GET:adunits':
				if ( ! isset( $data['accountID'] ) || ! isset( $data['clientID'] ) ) {
					$option            = $this->get_settings()->get();
					$data['accountID'] = $option['accountID'];
					if ( empty( $data['accountID'] ) ) {
						/* translators: %s: Missing parameter name */
						return new WP_Error( 'missing_required_param', sprintf( __( 'Request parameter is empty: %s.', 'google-site-kit' ), 'accountID' ), array( 'status' => 400 ) );
					}
					$data['clientID'] = $option['clientID'];
					if ( empty( $data['clientID'] ) ) {
						/* translators: %s: Missing parameter name */
						return new WP_Error( 'missing_required_param', sprintf( __( 'Request parameter is empty: %s.', 'google-site-kit' ), 'clientID' ), array( 'status' => 400 ) );
					}
				}
				$service = $this->get_service( 'adsense' );
				return $service->accounts_adclients_adunits->listAccountsAdclientsAdunits( self::normalize_client_id( $data['accountID'], $data['clientID'] ) );
			case 'GET:alerts':
				if ( ! isset( $data['accountID'] ) ) {
					$option            = $this->get_settings()->get();
					$data['accountID'] = $option['accountID'];
					if ( empty( $data['accountID'] ) ) {
						/* translators: %s: Missing parameter name */
						return new WP_Error( 'missing_required_param', sprintf( __( 'Request parameter is empty: %s.', 'google-site-kit' ), 'accountID' ), array( 'status' => 400 ) );
					}
				}
				$service = $this->get_service( 'adsense' );
				return $service->accounts_alerts->listAccountsAlerts( self::normalize_account_id( $data['accountID'] ) );
			case 'GET:clients':
				if ( ! isset( $data['accountID'] ) ) {
					return new WP_Error(
						'missing_required_param',
						/* translators: %s: Missing parameter name */
						sprintf( __( 'Request parameter is empty: %s.', 'google-site-kit' ), 'accountID' ),
						array( 'status' => 400 )
					);
				}
				$service = $this->get_service( 'adsense' );
				return $service->accounts_adclients->listAccountsAdclients( self::normalize_account_id( $data['accountID'] ) );
			case 'GET:report':
				$start_date = $data['startDate'];
				$end_date   = $data['endDate'];
				if ( ! strtotime( $start_date ) || ! strtotime( $end_date ) ) {
					$dates = $this->date_range_to_dates( $data['dateRange'] ?: 'last-28-days' );
					if ( is_wp_error( $dates ) ) {
						return $dates;
					}
					list ( $start_date, $end_date ) = $dates;
				}
				$args = array(
					'start_date' => $start_date,
					'end_date'   => $end_date,
				);
				$metrics = $this->parse_string_list( $data['metrics'] );
				if ( ! empty( $metrics ) ) {
					try {
						$this->validate_report_metrics( $metrics );
					} catch ( Invalid_Report_Metrics_Exception $exception ) {
						return new WP_Error(
							'invalid_adsense_report_metrics',
							$exception->getMessage()
						);
					}
					$args['metrics'] = $metrics;
				}
				$dimensions = $this->parse_string_list( $data['dimensions'] );
				if ( ! empty( $dimensions ) ) {
					try {
						$this->validate_report_dimensions( $dimensions );
					} catch ( Invalid_Report_Dimensions_Exception $exception ) {
						return new WP_Error(
							'invalid_adsense_report_dimensions',
							$exception->getMessage()
						);
					}
					$args['dimensions'] = $dimensions;
				}
				$orderby = $this->parse_earnings_orderby( $data['orderby'] );
				if ( ! empty( $orderby ) ) {
					$args['sort'] = $orderby;
				}
				if ( ! empty( $data['limit'] ) ) {
					$args['limit'] = $data['limit'];
				}
				return $this->create_adsense_earning_data_request( array_filter( $args ) );
			case 'GET:notifications':
				return function() {
					$alerts = $this->get_data( 'alerts' );
					if ( is_wp_error( $alerts ) || empty( $alerts ) ) {
						return array();
					}
					$alerts = array_filter(
						$alerts,
						function( Google_Service_Adsense_Alert $alert ) {
							return 'SEVERE' === $alert->getSeverity();
						}
					);
					// There is no SEVERE alert, return empty.
					if ( empty( $alerts ) ) {
						return array();
					}
					/**
					 * First Alert
					 *
					 * @var Google_Service_Adsense_Alert $alert
					 */
					$alert = array_shift( $alerts );
					return array(
						array(
							'id'            => 'adsense-notification',
							'description'   => $alert->getMessage(),
							'isDismissible' => true,
							'format'        => 'large',
							'severity'      => 'win-info',
							'ctaURL'        => $this->get_account_url(),
							'ctaLabel'      => __( 'Go to AdSense', 'google-site-kit' ),
							'ctaTarget'     => '_blank',
						),
					);
				};
			case 'GET:sites':
				if ( ! isset( $data['accountID'] ) ) {
					return new WP_Error(
						'missing_required_param',
						/* translators: %s: Missing parameter name */
						sprintf( __( 'Request parameter is empty: %s.', 'google-site-kit' ), 'accountID' ),
						array( 'status' => 400 )
					);
				}
				$service = $this->get_service( 'adsense' );
				return $service->accounts_sites->listAccountsSites( self::normalize_account_id( $data['accountID'] ) );
			case 'GET:urlchannels':
				if ( ! isset( $data['accountID'] ) ) {
					return new WP_Error(
						'missing_required_param',
						/* translators: %s: Missing parameter name */
						sprintf( __( 'Request parameter is empty: %s.', 'google-site-kit' ), 'accountID' ),
						array( 'status' => 400 )
					);
				}
				if ( ! isset( $data['clientID'] ) ) {
					return new WP_Error(
						'missing_required_param',
						/* translators: %s: Missing parameter name */
						sprintf( __( 'Request parameter is empty: %s.', 'google-site-kit' ), 'clientID' ),
						array( 'status' => 400 )
					);
				}
				$service = $this->get_service( 'adsense' );
				return $service->accounts_adclients_urlchannels->listAccountsAdclientsUrlchannels( self::normalize_client_id( $data['accountID'], $data['clientID'] ) );
		}
		return parent::create_data_request( $data );
	}
	/**
	 * Parses a response for the given datapoint.
	 *
	 * @since 1.0.0
	 *
	 * @param Data_Request $data Data request object.
	 * @param mixed        $response Request response.
	 *
	 * @return mixed Parsed response data on success, or WP_Error on failure.
	 */
	protected function parse_data_response( Data_Request $data, $response ) {
		switch ( "{$data->method}:{$data->datapoint}" ) {
			case 'GET:accounts':
				$accounts = array_filter( $response->getAccounts(), array( self::class, 'is_account_not_closed' ) );
				return Sort::case_insensitive_list_sort(
					array_map( array( self::class, 'filter_account_with_ids' ), $accounts ),
					'displayName'
				);
			case 'GET:adunits':
				return array_map( array( self::class, 'filter_adunit_with_ids' ), $response->getAdUnits() );
			case 'GET:alerts':
				return $response->getAlerts();
			case 'GET:clients':
				return array_map( array( self::class, 'filter_client_with_ids' ), $response->getAdClients() );
			case 'GET:urlchannels':
				return $response->getUrlChannels();
			case 'GET:report':
				return $response;
			case 'GET:sites':
				return $response->getSites();
		}
		return parent::parse_data_response( $data, $response );
	}
	/**
	 * Checks for the state of an Account, whether closed or not.
	 *
	 * @since 1.73.0
	 *
	 * @param Google_Model $account Account model.
	 * @return bool Whether the account is not closed.
	 */
	public static function is_account_not_closed( $account ) {
		return 'CLOSED' !== $account->getState();
	}
	/**
	 * Gets the service URL for the current account or signup if none.
	 *
	 * @since 1.25.0
	 *
	 * @return string
	 */
	protected function get_account_url() {
		$profile = $this->authentication->profile();
		$option  = $this->get_settings()->get();
		$query   = array(
			'source'     => 'site-kit',
			'utm_source' => 'site-kit',
			'utm_medium' => 'wordpress_signup',
			'url'        => rawurlencode( $this->context->get_reference_site_url() ),
		);
		if ( ! empty( $option['accountID'] ) ) {
			$url = sprintf( 'https://www.google.com/adsense/new/%s/home', $option['accountID'] );
		} else {
			$url = 'https://www.google.com/adsense/signup';
		}
		if ( $profile->has() ) {
			$query['authuser'] = $profile->get()['email'];
		}
		return add_query_arg( $query, $url );
	}
	/**
	 * Parses the orderby value of the data request into an array of earning orderby format.
	 *
	 * @since 1.15.0
	 *
	 * @param array|null $orderby Data request orderby value.
	 * @return string[] An array of reporting orderby strings.
	 */
	protected function parse_earnings_orderby( $orderby ) {
		if ( empty( $orderby ) || ! is_array( $orderby ) ) {
			return array();
		}
		$results = array_map(
			function ( $order_def ) {
				$order_def = array_merge(
					array(
						'fieldName' => '',
						'sortOrder' => '',
					),
					(array) $order_def
				);
				if ( empty( $order_def['fieldName'] ) || empty( $order_def['sortOrder'] ) ) {
					return null;
				}
				return ( 'ASCENDING' === $order_def['sortOrder'] ? '+' : '-' ) . $order_def['fieldName'];
			},
			// When just object is passed we need to convert it to an array of objects.
			wp_is_numeric_array( $orderby ) ? $orderby : array( $orderby )
		);
		$results = array_filter( $results );
		$results = array_values( $results );
		return $results;
	}
	/**
	 * Gets an array of dates for the given named date range.
	 *
	 * @param string $date_range Named date range.
	 *                           E.g. 'last-28-days'.
	 *
	 * @return array|WP_Error Array of [startDate, endDate] or WP_Error if invalid named range.
	 */
	private function date_range_to_dates( $date_range ) {
		switch ( $date_range ) {
			case 'today':
				return array(
					gmdate( 'Y-m-d', strtotime( 'today' ) ),
					gmdate( 'Y-m-d', strtotime( 'today' ) ),
				);
			// Intentional fallthrough.
			case 'last-7-days':
			case 'last-14-days':
			case 'last-28-days':
			case 'last-90-days':
				return $this->parse_date_range( $date_range );
		}
		return new WP_Error( 'invalid_date_range', __( 'Invalid date range.', 'google-site-kit' ) );
	}
	/**
	 * Creates a new AdSense earning request for the current account, site and given arguments.
	 *
	 * @since 1.0.0
	 *
	 * @param array $args {
	 *     Optional. Additional arguments.
	 *
	 *     @type array  $dimensions List of request dimensions. Default empty array.
	 *     @type array  $metrics    List of request metrics. Default empty array.
	 *     @type string $start_date Start date in 'Y-m-d' format. Default empty string.
	 *     @type string $end_date   End date in 'Y-m-d' format. Default empty string.
	 *     @type int    $row_limit  Limit of rows to return. Default none (will be skipped).
	 * }
	 * @return RequestInterface|WP_Error AdSense earning request instance.
	 */
	protected function create_adsense_earning_data_request( array $args = array() ) {
		$args = wp_parse_args(
			$args,
			array(
				'dimensions' => array(),
				'metrics'    => array(),
				'start_date' => '',
				'end_date'   => '',
				'limit'      => '',
				'sort'       => array(),
			)
		);
		$option     = $this->get_settings()->get();
		$account_id = $option['accountID'];
		if ( empty( $account_id ) ) {
			return new WP_Error( 'account_id_not_set', __( 'AdSense account ID not set.', 'google-site-kit' ) );
		}
		list( $start_year, $start_month, $start_day ) = explode( '-', $args['start_date'] );
		list( $end_year, $end_month, $end_day )       = explode( '-', $args['end_date'] );
		$opt_params = array(
			// In the AdSense API v2, date parameters require the individual pieces to be specified as integers.
			// See https://developers.google.com/adsense/management/reference/rest/v2/accounts.reports/generate.
			'dateRange'       => 'CUSTOM',
			'startDate.year'  => (int) $start_year,
			'startDate.month' => (int) $start_month,
			'startDate.day'   => (int) $start_day,
			'endDate.year'    => (int) $end_year,
			'endDate.month'   => (int) $end_month,
			'endDate.day'     => (int) $end_day,
			'languageCode'    => $this->context->get_locale( 'site', 'language-code' ),
			// Include default metrics only for backward-compatibility.
			'metrics'         => array( 'ESTIMATED_EARNINGS', 'PAGE_VIEWS_RPM', 'IMPRESSIONS' ),
		);
		if ( ! empty( $args['dimensions'] ) ) {
			$opt_params['dimensions'] = (array) $args['dimensions'];
		}
		if ( ! empty( $args['metrics'] ) ) {
			$opt_params['metrics'] = (array) $args['metrics'];
		}
		if ( ! empty( $args['sort'] ) ) {
			$opt_params['orderBy'] = (array) $args['sort'];
		}
		if ( ! empty( $args['limit'] ) ) {
			$opt_params['limit'] = (int) $args['limit'];
		}
		// @see https://developers.google.com/adsense/management/reporting/filtering?hl=en#OR
		$site_hostname         = URL::parse( $this->context->get_reference_site_url(), PHP_URL_HOST );
		$opt_params['filters'] = join(
			',',
			array_map(
				function ( $hostname ) {
					return 'DOMAIN_NAME==' . $hostname;
				},
				$this->permute_site_hosts( $site_hostname )
			)
		);
		return $this->get_service( 'adsense' )
			->accounts_reports
			->generate(
				self::normalize_account_id( $account_id ),
				$opt_params
			);
	}
	/**
	 * Sets up information about the module.
	 *
	 * @since 1.0.0
	 *
	 * @return array Associative array of module info.
	 */
	protected function setup_info() {
		$idenfifier_args = array(
			'source' => 'site-kit',
			'url'    => $this->context->get_reference_site_url(),
		);
		return array(
			'slug'        => self::MODULE_SLUG,
			'name'        => _x( 'AdSense', 'Service name', 'google-site-kit' ),
			'description' => __( 'Earn money by placing ads on your website. It’s free and easy.', 'google-site-kit' ),
			'order'       => 2,
			'homepage'    => add_query_arg( $idenfifier_args, 'https://www.google.com/adsense/start' ),
		);
	}
	/**
	 * Sets up the Google services the module should use.
	 *
	 * This method is invoked once by {@see Module::get_service()} to lazily set up the services when one is requested
	 * for the first time.
	 *
	 * @since 1.0.0
	 * @since 1.2.0 Now requires Google_Site_Kit_Client instance.
	 *
	 * @param Google_Site_Kit_Client $client Google client instance.
	 * @return array Google services as $identifier => $service_instance pairs. Every $service_instance must be an
	 *               instance of Google_Service.
	 */
	protected function setup_services( Google_Site_Kit_Client $client ) {
		return array(
			'adsense' => new Google_Service_Adsense( $client ),
		);
	}
	/**
	 * Sets up the module's settings instance.
	 *
	 * @since 1.2.0
	 *
	 * @return Module_Settings
	 */
	protected function setup_settings() {
		return new Settings( $this->options );
	}
	/**
	 * Sets up the module's assets to register.
	 *
	 * @since 1.9.0
	 *
	 * @return Asset[] List of Asset objects.
	 */
	protected function setup_assets() {
		$base_url = $this->context->url( 'dist/assets/' );
		return array(
			new Script(
				'googlesitekit-modules-adsense',
				array(
					'src'          => $base_url . 'js/googlesitekit-modules-adsense.js',
					'dependencies' => array(
						'googlesitekit-vendor',
						'googlesitekit-api',
						'googlesitekit-data',
						'googlesitekit-modules',
						'googlesitekit-datastore-site',
						'googlesitekit-datastore-user',
						'googlesitekit-components',
					),
				)
			),
		);
	}
	/**
	 * Registers the AdSense tag.
	 *
	 * @since 1.24.0
	 */
	private function register_tag() {
		// TODO: 'amp_story' support can be phased out in the long term.
		if ( is_singular( array( 'amp_story' ) ) ) {
			return;
		}
		$module_settings = $this->get_settings();
		$settings        = $module_settings->get();
		if ( $this->context->is_amp() ) {
			$tag = new AMP_Tag( $settings['clientID'], self::MODULE_SLUG );
			$tag->set_story_ad_slot_id( $settings['webStoriesAdUnit'] );
		} else {
			$tag = new Web_Tag( $settings['clientID'], self::MODULE_SLUG );
		}
		if ( ! $tag->is_tag_blocked() ) {
			$tag->use_guard( new Tag_Verify_Guard( $this->context->input() ) );
			$tag->use_guard( new Tag_Guard( $module_settings ) );
			$tag->use_guard( new Auto_Ad_Guard( $module_settings ) );
			$tag->use_guard( new Tag_Environment_Type_Guard() );
			if ( $tag->can_register() ) {
				$tag->register();
			}
		}
	}
	/**
	 * Parses account ID, adds it to the model object and returns updated model.
	 *
	 * @since 1.36.0
	 *
	 * @param Google_Model $account Account model.
	 * @param string       $id_key Attribute name that contains account ID.
	 * @return \stdClass Updated model with _id attribute.
	 */
	public static function filter_account_with_ids( $account, $id_key = 'name' ) {
		$obj = $account->toSimpleObject();
		$matches = array();
		if ( preg_match( '#accounts/([^/]+)#', $account[ $id_key ], $matches ) ) {
			$obj->_id = $matches[1];
		}
		return $obj;
	}
	/**
	 * Parses account and client IDs, adds it to the model object and returns updated model.
	 *
	 * @since 1.36.0
	 *
	 * @param Google_Model $client Client model.
	 * @param string       $id_key Attribute name that contains client ID.
	 * @return \stdClass Updated model with _id and _accountID attributes.
	 */
	public static function filter_client_with_ids( $client, $id_key = 'name' ) {
		$obj = $client->toSimpleObject();
		$matches = array();
		if ( preg_match( '#accounts/([^/]+)/adclients/([^/]+)#', $client[ $id_key ], $matches ) ) {
			$obj->_id        = $matches[2];
			$obj->_accountID = $matches[1]; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
		}
		return $obj;
	}
	/**
	 * Parses account, client and ad unit IDs, adds it to the model object and returns updated model.
	 *
	 * @since 1.36.0
	 *
	 * @param Google_Model $adunit Ad unit model.
	 * @param string       $id_key Attribute name that contains ad unit ID.
	 * @return \stdClass Updated model with _id, _clientID and _accountID attributes.
	 */
	public static function filter_adunit_with_ids( $adunit, $id_key = 'name' ) {
		$obj = $adunit->toSimpleObject();
		$matches = array();
		if ( preg_match( '#accounts/([^/]+)/adclients/([^/]+)/adunits/([^/]+)#', $adunit[ $id_key ], $matches ) ) {
			$obj->_id        = $matches[3];
			$obj->_clientID  = $matches[2]; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
			$obj->_accountID = $matches[1]; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
		}
		return $obj;
	}
	/**
	 * Normalizes account ID and returns it.
	 *
	 * @since 1.36.0
	 *
	 * @param string $account_id Account ID.
	 * @return string Updated account ID with "accounts/" prefix.
	 */
	public static function normalize_account_id( $account_id ) {
		return 'accounts/' . $account_id;
	}
	/**
	 * Normalizes ad client ID and returns it.
	 *
	 * @since 1.36.0
	 *
	 * @param string $account_id Account ID.
	 * @param string $client_id  Ad client ID.
	 * @return string Account ID and ad client ID in "accounts/{accountID}/adclients/{clientID}" format.
	 */
	public static function normalize_client_id( $account_id, $client_id ) {
		return 'accounts/' . $account_id . '/adclients/' . $client_id;
	}
	/**
	 * Outputs the Adsense for Platforms meta tags.
	 *
	 * @since 1.43.0
	 */
	private function render_platform_meta_tags() {
		printf( "\n\n", esc_html__( 'Google AdSense snippet added by Site Kit', 'google-site-kit' ) );
		echo '';
		echo "\n";
		echo '';
		printf( "\n\n", esc_html__( 'End Google AdSense snippet added by Site Kit', 'google-site-kit' ) );
	}
	/**
	 * Checks if the current user has access to the current configured service entity.
	 *
	 * @since 1.70.0
	 *
	 * @return boolean|WP_Error
	 */
	public function check_service_entity_access() {
		$data_request = array(
			'start_date' => gmdate( 'Y-m-d' ),
			'end_date'   => gmdate( 'Y-m-d' ),
			'limit'      => 1,
		);
		try {
			$request = $this->create_adsense_earning_data_request( $data_request );
			if ( is_wp_error( $request ) ) {
				return $request;
			}
		} catch ( Exception $e ) {
			if ( $e->getCode() === 403 ) {
				return false;
			}
			return $this->exception_to_error( $e );
		}
		return true;
	}
	/**
	 * Validates the report metrics.
	 *
	 * @since 1.83.0
	 *
	 * @param string[] $metrics The metrics to validate.
	 * @throws Invalid_Report_Metrics_Exception Thrown if the metrics are invalid.
	 */
	protected function validate_report_metrics( $metrics ) {
		if ( false === $this->is_using_shared_credentials ) {
			return;
		}
		$valid_metrics = apply_filters(
			'googlesitekit_shareable_adsense_metrics',
			array(
				'ESTIMATED_EARNINGS',
				'IMPRESSIONS',
				'PAGE_VIEWS_CTR',
				'PAGE_VIEWS_RPM',
			)
		);
		$invalid_metrics = array_diff( $metrics, $valid_metrics );
		if ( count( $invalid_metrics ) > 0 ) {
			$message = count( $invalid_metrics ) > 1 ? sprintf(
				/* translators: %s: is replaced with a comma separated list of the invalid metrics. */
				__(
					'Unsupported metrics requested: %s',
					'google-site-kit'
				),
				join(
					/* translators: used between list items, there is a space after the comma. */
					__( ', ', 'google-site-kit' ),
					$invalid_metrics
				)
			) : sprintf(
				/* translators: %s: is replaced with the invalid metric. */
				__(
					'Unsupported metric requested: %s',
					'google-site-kit'
				),
				$invalid_metrics
			);
			throw new Invalid_Report_Metrics_Exception( $message );
		}
	}
	/**
	 * Validates the report dimensions.
	 *
	 * @since 1.83.0
	 *
	 * @param string[] $dimensions The dimensions to validate.
	 * @throws Invalid_Report_Dimensions_Exception Thrown if the dimensions are invalid.
	 */
	protected function validate_report_dimensions( $dimensions ) {
		if ( false === $this->is_using_shared_credentials ) {
			return;
		}
		$valid_dimensions = apply_filters(
			'googlesitekit_shareable_adsense_dimensions',
			array(
				'DATE',
			)
		);
		$invalid_dimensions = array_diff( $dimensions, $valid_dimensions );
		if ( count( $invalid_dimensions ) > 0 ) {
			$message = count( $invalid_dimensions ) > 1 ? sprintf(
				/* translators: %s: is replaced with a comma separated list of the invalid dimensions. */
				__(
					'Unsupported dimensions requested: %s',
					'google-site-kit'
				),
				join(
					/* translators: used between list items, there is a space after the comma. */
					__( ', ', 'google-site-kit' ),
					$invalid_dimensions
				)
			) : sprintf(
				/* translators: %s: is replaced with the invalid dimension. */
				__(
					'Unsupported dimension requested: %s',
					'google-site-kit'
				),
				$invalid_dimensions
			);
			throw new Invalid_Report_Dimensions_Exception( $message );
		}
	}
}
                                                  PageSpeed_Insights.php                                                                              0000644                 00000014435 15051555666 0011007 0                                                                                                    ustar 00                                                                                                                                                                                                                                                       get_settings()->delete();
	}
	/**
	 * Gets map of datapoint to definition data for each.
	 *
	 * @since 1.12.0
	 *
	 * @return array Map of datapoints to their definitions.
	 */
	protected function get_datapoint_definitions() {
		return array(
			'GET:pagespeed' => array(
				'service'   => 'pagespeedonline',
				'shareable' => Feature_Flags::enabled( 'dashboardSharing' ),
			),
		);
	}
	/**
	 * Creates a request object for the given datapoint.
	 *
	 * @since 1.0.0
	 *
	 * @param Data_Request $data Data request object.
	 * @return RequestInterface|callable|WP_Error Request object or callable on success, or WP_Error on failure.
	 *
	 * @throws Invalid_Datapoint_Exception Thrown if the datapoint does not exist.
	 */
	protected function create_data_request( Data_Request $data ) {
		switch ( "{$data->method}:{$data->datapoint}" ) {
			case 'GET:pagespeed':
				if ( empty( $data['strategy'] ) ) {
					return new WP_Error(
						'missing_required_param',
						sprintf(
							/* translators: %s: Missing parameter name */
							__( 'Request parameter is empty: %s.', 'google-site-kit' ),
							'strategy'
						),
						array( 'status' => 400 )
					);
				}
				$valid_strategies = array( 'mobile', 'desktop' );
				if ( ! in_array( $data['strategy'], $valid_strategies, true ) ) {
					return new WP_Error(
						'invalid_param',
						sprintf(
							/* translators: 1: Invalid parameter name, 2: list of valid values */
							__( 'Request parameter %1$s is not one of %2$s', 'google-site-kit' ),
							'strategy',
							implode( ', ', $valid_strategies )
						),
						array( 'status' => 400 )
					);
				}
				if ( ! empty( $data['url'] ) ) {
					$page_url = $data['url'];
				} else {
					$page_url = $this->context->get_reference_site_url();
				}
				$service = $this->get_service( 'pagespeedonline' );
				return $service->pagespeedapi->runpagespeed(
					$page_url,
					array(
						'locale'   => $this->context->get_locale( 'site', 'language-code' ),
						'strategy' => $data['strategy'],
					)
				);
		}
		return parent::create_data_request( $data );
	}
	/**
	 * Sets up the module's assets to register.
	 *
	 * @since 1.9.0
	 *
	 * @return Asset[] List of Asset objects.
	 */
	protected function setup_assets() {
		$base_url = $this->context->url( 'dist/assets/' );
		return array(
			new Script(
				'googlesitekit-modules-pagespeed-insights',
				array(
					'src'          => $base_url . 'js/googlesitekit-modules-pagespeed-insights.js',
					'dependencies' => array(
						'googlesitekit-vendor',
						'googlesitekit-api',
						'googlesitekit-data',
						'googlesitekit-modules',
						'googlesitekit-datastore-site',
						'googlesitekit-components',
					),
				)
			),
		);
	}
	/**
	 * Sets up information about the module.
	 *
	 * @since 1.0.0
	 *
	 * @return array Associative array of module info.
	 */
	protected function setup_info() {
		return array(
			'slug'        => 'pagespeed-insights',
			'name'        => _x( 'PageSpeed Insights', 'Service name', 'google-site-kit' ),
			'description' => __( 'Google PageSpeed Insights gives you metrics about performance, accessibility, SEO and PWA', 'google-site-kit' ),
			'order'       => 4,
			'homepage'    => __( 'https://pagespeed.web.dev', 'google-site-kit' ),
		);
	}
	/**
	 * Sets up the module's settings instance.
	 *
	 * @since 1.49.0
	 *
	 * @return Module_Settings
	 */
	protected function setup_settings() {
		return new Settings( $this->options );
	}
	/**
	 * Sets up the Google services the module should use.
	 *
	 * This method is invoked once by {@see Module::get_service()} to lazily set up the services when one is requested
	 * for the first time.
	 *
	 * @since 1.0.0
	 * @since 1.2.0 Now requires Google_Site_Kit_Client instance.
	 *
	 * @param Google_Site_Kit_Client $client Google client instance.
	 * @return array Google services as $identifier => $service_instance pairs. Every $service_instance must be an
	 *               instance of Google_Service.
	 */
	protected function setup_services( Google_Site_Kit_Client $client ) {
		return array(
			'pagespeedonline' => new Google_Service_PagespeedInsights( $client ),
		);
	}
	/**
	 * Gets required Google OAuth scopes for the module.
	 *
	 * @return array List of Google OAuth scopes.
	 * @since 1.0.0
	 */
	public function get_scopes() {
		return array(
			'openid',
		);
	}
}
                                                                                                                                                                                                                                   Tag_Manager.php                                                                                     0000644                 00000052066 15051555666 0007451 0                                                                                                    ustar 00                                                                                                                                                                                                                                                        'containerID',
		self::USAGE_CONTEXT_AMP => 'ampContainerID',
	);
	/**
	 * Registers functionality through WordPress hooks.
	 *
	 * @since 1.0.0
	 */
	public function register() {
		$this->register_scopes_hook();
		// Tag Manager tag placement logic.
		add_action( 'template_redirect', $this->get_method_proxy( 'register_tag' ) );
		// Filter the Analytics `canUseSnippet` value.
		add_filter( 'googlesitekit_analytics_can_use_snippet', $this->get_method_proxy( 'can_analytics_use_snippet' ), 10, 2 );
		// Filter whether certain users can be excluded from tracking.
		add_action( 'googlesitekit_allow_tracking_disabled', $this->get_method_proxy( 'filter_analytics_allow_tracking_disabled' ) );
		add_action( 'googlesitekit_analytics_tracking_opt_out', $this->get_method_proxy( 'analytics_tracking_opt_out' ) );
	}
	/**
	 * Gets required Google OAuth scopes for the module.
	 *
	 * @since 1.0.0
	 *
	 * @return array List of Google OAuth scopes.
	 */
	public function get_scopes() {
		return array(
			'https://www.googleapis.com/auth/tagmanager.readonly',
		);
	}
	/**
	 * Checks whether the module is connected.
	 *
	 * A module being connected means that all steps required as part of its activation are completed.
	 *
	 * @since 1.0.0
	 *
	 * @return bool True if module is connected, false otherwise.
	 */
	public function is_connected() {
		$settings = $this->get_settings()->get();
		$amp_mode = $this->context->get_amp_mode();
		switch ( $amp_mode ) {
			case Context::AMP_MODE_PRIMARY:
				$container_ids = array( $settings['ampContainerID'] );
				break;
			case Context::AMP_MODE_SECONDARY:
				$container_ids = array( $settings['containerID'], $settings['ampContainerID'] );
				break;
			default:
				$container_ids = array( $settings['containerID'] );
		}
		$container_id_errors = array_filter(
			$container_ids,
			function( $container_id ) {
				return ! $container_id;
			}
		);
		if ( ! empty( $container_id_errors ) ) {
			return false;
		}
		return parent::is_connected();
	}
	/**
	 * Cleans up when the module is deactivated.
	 *
	 * @since 1.0.0
	 */
	public function on_deactivation() {
		$this->get_settings()->delete();
	}
	/**
	 * Gets an array of debug field definitions.
	 *
	 * @since 1.5.0
	 *
	 * @return array
	 */
	public function get_debug_fields() {
		$settings = $this->get_settings()->get();
		return array(
			'tagmanager_account_id'       => array(
				'label' => __( 'Tag Manager account ID', 'google-site-kit' ),
				'value' => $settings['accountID'],
				'debug' => Debug_Data::redact_debug_value( $settings['accountID'] ),
			),
			'tagmanager_container_id'     => array(
				'label' => __( 'Tag Manager container ID', 'google-site-kit' ),
				'value' => $settings['containerID'],
				'debug' => Debug_Data::redact_debug_value( $settings['containerID'], 7 ),
			),
			'tagmanager_amp_container_id' => array(
				'label' => __( 'Tag Manager AMP container ID', 'google-site-kit' ),
				'value' => $settings['ampContainerID'],
				'debug' => Debug_Data::redact_debug_value( $settings['ampContainerID'], 7 ),
			),
			'tagmanager_use_snippet'      => array(
				'label' => __( 'Tag Manager snippet placed', 'google-site-kit' ),
				'value' => $settings['useSnippet'] ? __( 'Yes', 'google-site-kit' ) : __( 'No', 'google-site-kit' ),
				'debug' => $settings['useSnippet'] ? 'yes' : 'no',
			),
		);
	}
	/**
	 * Sanitizes a string to be used for a container name.
	 *
	 * @since 1.0.4
	 *
	 * @param string $name String to sanitize.
	 *
	 * @return string
	 */
	public static function sanitize_container_name( $name ) {
		// Remove any leading or trailing whitespace.
		$name = trim( $name );
		// Must not start with an underscore.
		$name = ltrim( $name, '_' );
		// Decode entities for special characters so that they are stripped properly.
		$name = wp_specialchars_decode( $name, ENT_QUOTES );
		// Convert accents to basic characters to prevent them from being stripped.
		$name = remove_accents( $name );
		// Strip all non-simple characters.
		$name = preg_replace( '/[^a-zA-Z0-9_., -]/', '', $name );
		// Collapse multiple whitespaces.
		$name = preg_replace( '/\s+/', ' ', $name );
		return $name;
	}
	/**
	 * Gets map of datapoint to definition data for each.
	 *
	 * @since 1.9.0
	 *
	 * @return array Map of datapoints to their definitions.
	 */
	protected function get_datapoint_definitions() {
		return array(
			'GET:accounts'               => array( 'service' => 'tagmanager' ),
			'GET:accounts-containers'    => array( 'service' => 'tagmanager' ),
			'GET:containers'             => array( 'service' => 'tagmanager' ),
			'POST:create-container'      => array(
				'service'                => 'tagmanager',
				'scopes'                 => array( 'https://www.googleapis.com/auth/tagmanager.edit.containers' ),
				'request_scopes_message' => __( 'Additional permissions are required to create a new Tag Manager container on your behalf.', 'google-site-kit' ),
			),
			'GET:live-container-version' => array( 'service' => 'tagmanager' ),
		);
	}
	/**
	 * Creates a request object for the given datapoint.
	 *
	 * @since 1.0.0
	 *
	 * @param Data_Request $data Data request object.
	 * @return RequestInterface|callable|WP_Error Request object or callable on success, or WP_Error on failure.
	 *
	 * @throws Invalid_Datapoint_Exception Thrown if the datapoint does not exist.
	 */
	protected function create_data_request( Data_Request $data ) {
		switch ( "{$data->method}:{$data->datapoint}" ) {
			// Intentional fallthrough.
			case 'GET:accounts':
			case 'GET:accounts-containers':
				return $this->get_tagmanager_service()->accounts->listAccounts();
			case 'GET:containers':
				if ( ! isset( $data['accountID'] ) ) {
					/* translators: %s: Missing parameter name */
					return new WP_Error( 'missing_required_param', sprintf( __( 'Request parameter is empty: %s.', 'google-site-kit' ), 'accountID' ), array( 'status' => 400 ) );
				}
				return $this->get_tagmanager_service()->accounts_containers->listAccountsContainers( "accounts/{$data['accountID']}" );
			case 'POST:create-container':
				if ( ! isset( $data['accountID'] ) ) {
					return new WP_Error(
						'missing_required_param',
						/* translators: %s: Missing parameter name */
						sprintf( __( 'Request parameter is empty: %s.', 'google-site-kit' ), 'accountID' ),
						array( 'status' => 400 )
					);
				}
				$usage_context = $data['usageContext'] ?: array( self::USAGE_CONTEXT_WEB, self::USAGE_CONTEXT_AMP );
				if ( empty( $this->context_map[ $usage_context ] ) ) {
					return new WP_Error(
						'invalid_param',
						sprintf(
						/* translators: 1: Invalid parameter name, 2: list of valid values */
							__( 'Request parameter %1$s is not one of %2$s', 'google-site-kit' ),
							'usageContext',
							implode( ', ', array_keys( $this->context_map ) )
						),
						array( 'status' => 400 )
					);
				}
				$account_id = $data['accountID'];
				if ( $data['name'] ) {
					$container_name = $data['name'];
				} else {
					// Use site name for container, fallback to domain of reference URL.
					$container_name = get_bloginfo( 'name' ) ?: URL::parse( $this->context->get_reference_site_url(), PHP_URL_HOST );
					// Prevent naming conflict (Tag Manager does not allow more than one with same name).
					if ( self::USAGE_CONTEXT_AMP === $usage_context ) {
						$container_name .= ' AMP';
					}
				}
				$container = new Google_Service_TagManager_Container();
				$container->setName( self::sanitize_container_name( $container_name ) );
				$container->setUsageContext( (array) $usage_context );
				return $this->get_tagmanager_service()->accounts_containers->create( "accounts/{$account_id}", $container );
			case 'GET:live-container-version':
				if ( ! isset( $data['accountID'] ) ) {
					return new WP_Error(
						'missing_required_param',
						/* translators: %s: Missing parameter name */
						sprintf( __( 'Request parameter is empty: %s.', 'google-site-kit' ), 'accountID' ),
						array( 'status' => 400 )
					);
				}
				if ( ! isset( $data['internalContainerID'] ) ) {
					return new WP_Error(
						'missing_required_param',
						/* translators: %s: Missing parameter name */
						sprintf( __( 'Request parameter is empty: %s.', 'google-site-kit' ), 'internalContainerID' ),
						array( 'status' => 400 )
					);
				}
				return $this->get_tagmanager_service()->accounts_containers_versions->live(
					"accounts/{$data['accountID']}/containers/{$data['internalContainerID']}"
				);
		}
		return parent::create_data_request( $data );
	}
	/**
	 * Creates GTM Container.
	 *
	 * @since 1.0.0
	 * @param string       $account_id    The account ID.
	 * @param string|array $usage_context The container usage context(s).
	 *
	 * @return string Container public ID.
	 * @throws Exception Throws an exception if raised during container creation.
	 */
	protected function create_container( $account_id, $usage_context = self::USAGE_CONTEXT_WEB ) {
		$restore_defer = $this->with_client_defer( false );
		// Use site name for container, fallback to domain of reference URL.
		$container_name = get_bloginfo( 'name' ) ?: URL::parse( $this->context->get_reference_site_url(), PHP_URL_HOST );
		// Prevent naming conflict (Tag Manager does not allow more than one with same name).
		if ( self::USAGE_CONTEXT_AMP === $usage_context ) {
			$container_name .= ' AMP';
		}
		$container_name = self::sanitize_container_name( $container_name );
		$container = new Google_Service_TagManager_Container();
		$container->setName( $container_name );
		$container->setUsageContext( (array) $usage_context );
		try {
			$new_container = $this->get_tagmanager_service()->accounts_containers->create( "accounts/{$account_id}", $container );
		} catch ( Exception $exception ) {
			$restore_defer();
			throw $exception;
		}
		$restore_defer();
		return $new_container->getPublicId();
	}
	/**
	 * Parses a response for the given datapoint.
	 *
	 * @since 1.0.0
	 *
	 * @param Data_Request $data Data request object.
	 * @param mixed        $response Request response.
	 *
	 * @return mixed Parsed response data on success, or WP_Error on failure.
	 */
	protected function parse_data_response( Data_Request $data, $response ) {
		switch ( "{$data->method}:{$data->datapoint}" ) {
			case 'GET:accounts':
				/* @var Google_Service_TagManager_ListAccountsResponse $response List accounts response. */
				return Sort::case_insensitive_list_sort(
					$response->getAccount(),
					'name'
				);
			case 'GET:accounts-containers':
				/* @var Google_Service_TagManager_ListAccountsResponse $response List accounts response. */
				$accounts = Sort::case_insensitive_list_sort(
					$response->getAccount(),
					'name'
				);
				$response = array(
					// TODO: Parse this response to a regular array.
					'accounts'   => $accounts,
					'containers' => array(),
				);
				if ( 0 === count( $response['accounts'] ) ) {
					return $response;
				}
				if ( $data['accountID'] ) {
					$account_id = $data['accountID'];
				} else {
					$account_id = $response['accounts'][0]->getAccountId();
				}
				$containers = $this->get_data(
					'containers',
					array(
						'accountID'    => $account_id,
						'usageContext' => $data['usageContext'] ?: self::USAGE_CONTEXT_WEB,
					)
				);
				if ( is_wp_error( $containers ) ) {
					return $response;
				}
				return array_merge( $response, compact( 'containers' ) );
			case 'GET:containers':
				/* @var Google_Service_TagManager_ListContainersResponse $response Response object. */
				$usage_context = $data['usageContext'] ?: array( self::USAGE_CONTEXT_WEB, self::USAGE_CONTEXT_AMP );
				/* @var Google_Service_TagManager_Container[] $containers Filtered containers. */
				$containers = array_filter(
					(array) $response->getContainer(),
					function ( Google_Service_TagManager_Container $container ) use ( $usage_context ) {
						return array_intersect( (array) $usage_context, $container->getUsageContext() );
					}
				);
				return Sort::case_insensitive_list_sort(
					array_values( $containers ),
					'name'
				);
		}
		return parent::parse_data_response( $data, $response );
	}
	/**
	 * Gets the configured TagManager service instance.
	 *
	 * @since 1.2.0
	 *
	 * @return Google_Service_TagManager instance.
	 * @throws Exception Thrown if the module did not correctly set up the service.
	 */
	private function get_tagmanager_service() {
		return $this->get_service( 'tagmanager' );
	}
	/**
	 * Sets up information about the module.
	 *
	 * @since 1.0.0
	 *
	 * @return array Associative array of module info.
	 */
	protected function setup_info() {
		return array(
			'slug'        => self::MODULE_SLUG,
			'name'        => _x( 'Tag Manager', 'Service name', 'google-site-kit' ),
			'description' => __( 'Tag Manager creates an easy to manage way to create tags on your site without updating code', 'google-site-kit' ),
			'order'       => 6,
			'homepage'    => __( 'https://tagmanager.google.com/', 'google-site-kit' ),
		);
	}
	/**
	 * Sets up the Google services the module should use.
	 *
	 * This method is invoked once by {@see Module::get_service()} to lazily set up the services when one is requested
	 * for the first time.
	 *
	 * @since 1.0.0
	 * @since 1.2.0 Now requires Google_Site_Kit_Client instance.
	 *
	 * @param Google_Site_Kit_Client $client Google client instance.
	 * @return array Google services as $identifier => $service_instance pairs. Every $service_instance must be an
	 *               instance of Google_Service.
	 */
	protected function setup_services( Google_Site_Kit_Client $client ) {
		return array(
			'tagmanager' => new Google_Service_TagManager( $client ),
		);
	}
	/**
	 * Sets up the module's settings instance.
	 *
	 * @since 1.2.0
	 *
	 * @return Module_Settings
	 */
	protected function setup_settings() {
		return new Settings( $this->options );
	}
	/**
	 * Sets up the module's assets to register.
	 *
	 * @since 1.11.0
	 *
	 * @return Asset[] List of Asset objects.
	 */
	protected function setup_assets() {
		$base_url = $this->context->url( 'dist/assets/' );
		$dependencies = array(
			'googlesitekit-api',
			'googlesitekit-data',
			'googlesitekit-datastore-site',
			'googlesitekit-modules',
			'googlesitekit-vendor',
			'googlesitekit-components',
		);
		$analytics_exists = apply_filters( 'googlesitekit_module_exists', false, 'analytics' );
		// Note that the Tag Manager bundle will make use of the Analytics bundle if it's available,
		// but can also function without it, hence the conditional include of the Analytics bundle here.
		if ( $analytics_exists ) {
			$dependencies[] = 'googlesitekit-modules-analytics';
		}
		return array(
			new Script(
				'googlesitekit-modules-tagmanager',
				array(
					'src'          => $base_url . 'js/googlesitekit-modules-tagmanager.js',
					'dependencies' => $dependencies,
				)
			),
		);
	}
	/**
	 * Registers the Tag Manager tag.
	 *
	 * @since 1.24.0
	 */
	private function register_tag() {
		$is_amp          = $this->context->is_amp();
		$module_settings = $this->get_settings();
		$settings        = $module_settings->get();
		$tag = $is_amp
			? new AMP_Tag( $settings['ampContainerID'], self::MODULE_SLUG )
			: new Web_Tag( $settings['containerID'], self::MODULE_SLUG );
		if ( ! $tag->is_tag_blocked() ) {
			$tag->use_guard( new Tag_Verify_Guard( $this->context->input() ) );
			$tag->use_guard( new Tag_Guard( $module_settings, $is_amp ) );
			$tag->use_guard( new Tag_Environment_Type_Guard() );
			if ( $tag->can_register() ) {
				$tag->register();
			}
		}
	}
	/**
	 * Filters whether or not the Analytics module's snippet should be controlled by its `useSnippet` setting.
	 *
	 * @since 1.28.0
	 * @since 1.75.0 Now requires current UA property ID as second parameter.
	 *
	 * @param boolean $original_value Original value of useSnippet setting.
	 * @param string  $ua_property_id Current UA property.
	 * @return boolean Filtered value.
	 */
	private function can_analytics_use_snippet( $original_value, $ua_property_id ) {
		$settings = $this->get_settings()->get();
		if ( ! empty( $settings['gaPropertyID'] ) && $settings['useSnippet'] && $settings['gaPropertyID'] === $ua_property_id ) {
			return false;
		}
		return $original_value;
	}
	/**
	 * Handles Analytics measurement opt-out for the configured Analytics property in the container(s).
	 *
	 * @since 1.41.0
	 *
	 * @param string $property_id Analytics property_id.
	 */
	private function analytics_tracking_opt_out( $property_id ) {
		$settings       = $this->get_settings()->get();
		$ga_property_id = $settings['gaPropertyID'];
		if ( ! $ga_property_id || $ga_property_id === $property_id ) {
			return;
		}
		BC_Functions::wp_print_inline_script_tag(
			sprintf(
				'window["ga-disable-%s"] = true;',
				esc_attr( $ga_property_id )
			)
		);
	}
	/**
	 * Filters whether or not the option to exclude certain users from tracking should be displayed.
	 *
	 * If Site Kit does not place the Analytics snippet (neither via Analytics nor via Tag Manager),
	 * the option to exclude certain users from tracking should not be displayed.
	 *
	 * @since 1.36.0
	 *
	 * @param boolean $allowed Whether to allow tracking exclusion.
	 * @return boolean Filtered value.
	 */
	private function filter_analytics_allow_tracking_disabled( $allowed ) {
		if ( $allowed ) {
			return true;
		}
		$settings = $this->get_settings()->get();
		if ( ! empty( $settings['gaPropertyID'] ) && $settings['useSnippet'] ) {
			return true;
		}
		return $allowed;
	}
	/**
	 * Checks if the current user has access to the current configured service entity.
	 *
	 * @since 1.77.0
	 *
	 * @return boolean|WP_Error
	 */
	public function check_service_entity_access() {
		$is_amp_mode = in_array( $this->context->get_amp_mode(), array( Context::AMP_MODE_PRIMARY, Context::AMP_MODE_SECONDARY ), true );
		$settings   = $this->get_settings()->get();
		$account_id = $settings['accountID'];
		$configured_containers = $is_amp_mode ? array( $settings['containerID'], $settings['ampContainerID'] ) : array( $settings['containerID'] );
		try {
			$containers = $this->get_tagmanager_service()->accounts_containers->listAccountsContainers( "accounts/{$account_id}" );
		} catch ( Exception $e ) {
			if ( $e->getCode() === 403 ) {
				return false;
			}
			return $this->exception_to_error( $e );
		}
		$all_containers = array_map(
			function( $container ) {
				return $container->getPublicId();
			},
			$containers->getContainer()
		);
		return empty( array_diff( $configured_containers, $all_containers ) );
	}
}
                                                                                                                                                                                                                                                                                                                                                                                                                                                                          Site_Verification.php                                                                               0000644                 00000037573 15051555666 0010720 0                                                                                                    ustar 00                                                                                                                                                                                                                                                       register_scopes_hook();
		add_action(
			'googlesitekit_verify_site_ownership',
			$this->get_method_proxy( 'handle_verification_token' ),
			10,
			2
		);
		$print_site_verification_meta = function() {
			$this->print_site_verification_meta();
		};
		add_action( 'wp_head', $print_site_verification_meta );
		add_action( 'login_head', $print_site_verification_meta );
		add_action(
			'googlesitekit_authorize_user',
			function() {
				if ( ! $this->authentication->credentials()->using_proxy() ) {
					return;
				}
				$this->user_options->set( Verification::OPTION, 'verified' );
			}
		);
		add_action(
			'init',
			function () {
				$request_uri    = $this->context->input()->filter( INPUT_SERVER, 'REQUEST_URI' );
				$request_method = $this->context->input()->filter( INPUT_SERVER, 'REQUEST_METHOD' );
				if (
					( $request_uri && $request_method )
					&& 'GET' === strtoupper( $request_method )
					&& preg_match( '/^\/google(?P[a-z0-9]+)\.html$/', $request_uri, $matches )
				) {
					$this->serve_verification_file( $matches['token'] );
				}
			}
		);
		$clear_verification_meta_cache = function ( $meta_id, $object_id, $meta_key ) {
			if ( $this->user_options->get_meta_key( Verification_Meta::OPTION ) === $meta_key ) {
				( new Transients( $this->context ) )->delete( self::TRANSIENT_VERIFICATION_META_TAGS );
			}
		};
		add_action( 'added_user_meta', $clear_verification_meta_cache, 10, 3 );
		add_action( 'updated_user_meta', $clear_verification_meta_cache, 10, 3 );
		add_action( 'deleted_user_meta', $clear_verification_meta_cache, 10, 3 );
	}
	/**
	 * Gets required Google OAuth scopes for the module.
	 *
	 * @since 1.0.0
	 *
	 * @return array List of Google OAuth scopes.
	 */
	public function get_scopes() {
		return array(
			'https://www.googleapis.com/auth/siteverification',
		);
	}
	/**
	 * Gets map of datapoint to definition data for each.
	 *
	 * @since 1.12.0
	 *
	 * @return array Map of datapoints to their definitions.
	 */
	protected function get_datapoint_definitions() {
		return array(
			'GET:verification'       => array( 'service' => 'siteverification' ),
			'POST:verification'      => array( 'service' => 'siteverification' ),
			'GET:verification-token' => array( 'service' => 'siteverification' ),
			'GET:verified-sites'     => array( 'service' => 'siteverification' ),
		);
	}
	/**
	 * Creates a request object for the given datapoint.
	 *
	 * @since 1.0.0
	 *
	 * @param Data_Request $data Data request object.
	 * @return RequestInterface|callable|WP_Error Request object or callable on success, or WP_Error on failure.
	 *
	 * @throws Invalid_Datapoint_Exception Thrown if the datapoint does not exist.
	 */
	protected function create_data_request( Data_Request $data ) {
		switch ( "{$data->method}:{$data->datapoint}" ) {
			case 'GET:verification':
				return $this->get_siteverification_service()->webResource->listWebResource();
			case 'POST:verification':
				if ( ! isset( $data['siteURL'] ) ) {
					/* translators: %s: Missing parameter name */
					return new WP_Error( 'missing_required_param', sprintf( __( 'Request parameter is empty: %s.', 'google-site-kit' ), 'siteURL' ), array( 'status' => 400 ) );
				}
				return function() use ( $data ) {
					$current_user = wp_get_current_user();
					if ( ! $current_user || ! $current_user->exists() ) {
						return new WP_Error( 'unknown_user', __( 'Unknown user.', 'google-site-kit' ) );
					}
					$site = $this->get_data( 'verification', $data );
					if ( is_wp_error( $site ) ) {
						return $site;
					}
					$sites = array();
					if ( ! empty( $site['verified'] ) ) {
						$this->authentication->verification()->set( true );
						return $site;
					} else {
						$token = $this->get_data( 'verification-token', $data );
						if ( is_wp_error( $token ) ) {
							return $token;
						}
						$this->authentication->verification_meta()->set( $token['token'] );
						$restore_defer = $this->with_client_defer( false );
						$errors        = new WP_Error();
						foreach ( $this->permute_site_url( $data['siteURL'] ) as $url ) {
							$site = new Google_Service_SiteVerification_SiteVerificationWebResourceResourceSite();
							$site->setType( 'SITE' );
							$site->setIdentifier( $url );
							$resource = new Google_Service_SiteVerification_SiteVerificationWebResourceResource();
							$resource->setSite( $site );
							try {
								$sites[] = $this->get_siteverification_service()->webResource->insert( 'META', $resource );
							} catch ( Google_Service_Exception $e ) {
								$messages = wp_list_pluck( $e->getErrors(), 'message' );
								$message  = array_shift( $messages );
								$errors->add( $e->getCode(), $message, array( 'url' => $url ) );
							} catch ( Exception $e ) {
								$errors->add( $e->getCode(), $e->getMessage(), array( 'url' => $url ) );
							}
						}
						$restore_defer();
						if ( empty( $sites ) ) {
							return $errors;
						}
					}
					$this->authentication->verification()->set( true );
					try {
						$verification = $this->get_siteverification_service()->webResource->get( $data['siteURL'] );
					} catch ( Google_Service_Exception $e ) {
						$verification = array_shift( $sites );
					}
					return array(
						'identifier' => $verification->getSite()->getIdentifier(),
						'type'       => $verification->getSite()->getType(),
						'verified'   => true,
					);
				};
			case 'GET:verification-token':
				$existing_token = $this->authentication->verification_meta()->get();
				if ( ! empty( $existing_token ) ) {
					return function() use ( $existing_token ) {
						return array(
							'method' => 'META',
							'token'  => $existing_token,
						);
					};
				}
				$current_url = ! empty( $data['siteURL'] ) ? $data['siteURL'] : $this->context->get_reference_site_url();
				$site        = new Google_Service_SiteVerification_SiteVerificationWebResourceGettokenRequestSite();
				$site->setIdentifier( $current_url );
				$site->setType( 'SITE' );
				$request = new Google_Service_SiteVerification_SiteVerificationWebResourceGettokenRequest();
				$request->setSite( $site );
				$request->setVerificationMethod( 'META' );
				return $this->get_siteverification_service()->webResource->getToken( $request );
			case 'GET:verified-sites':
				return $this->get_siteverification_service()->webResource->listWebResource();
		}
		return parent::create_data_request( $data );
	}
	/**
	 * Parses a response for the given datapoint.
	 *
	 * @since 1.0.0
	 *
	 * @param Data_Request $data Data request object.
	 * @param mixed        $response Request response.
	 *
	 * @return mixed Parsed response data on success, or WP_Error on failure.
	 */
	protected function parse_data_response( Data_Request $data, $response ) {
		switch ( "{$data->method}:{$data->datapoint}" ) {
			case 'GET:verification':
				if ( $data['siteURL'] ) {
					$current_url = $data['siteURL'];
				} else {
					$current_url = $this->context->get_reference_site_url();
				}
				$items = $response->getItems();
				foreach ( $items as $item ) {
					$site = $item->getSite();
					$match = false;
					if ( 'INET_DOMAIN' === $site->getType() ) {
						$match = $this->is_domain_match( $site->getIdentifier(), $current_url );
					} elseif ( 'SITE' === $site->getType() ) {
						$match = $this->is_url_match( $site->getIdentifier(), $current_url );
					}
					if ( $match ) {
						return array(
							'identifier' => $site->getIdentifier(),
							'type'       => $site->getType(),
							'verified'   => true,
						);
					}
				}
				return array(
					'identifier' => $current_url,
					'type'       => 'SITE',
					'verified'   => false,
				);
			case 'GET:verification-token':
				if ( is_array( $response ) ) {
					return $response;
				}
				return array(
					'method' => $response->getMethod(),
					'token'  => $response->getToken(),
				);
			case 'GET:verified-sites':
				$items = $response->getItems();
				$data  = array();
				foreach ( $items as $item ) {
					$site                   = $item->getSite();
					$data[ $item->getId() ] = array(
						'identifier' => $site->getIdentifier(),
						'type'       => $site->getType(),
					);
				}
				return $data;
		}
		return parent::parse_data_response( $data, $response );
	}
	/**
	 * Sets up information about the module.
	 *
	 * @since 1.0.0
	 *
	 * @return array Associative array of module info.
	 */
	protected function setup_info() {
		return array(
			'slug'        => 'site-verification',
			'name'        => _x( 'Site Verification', 'Service name', 'google-site-kit' ),
			'description' => __( 'Google Site Verification allows you to manage ownership of your site.', 'google-site-kit' ),
			'order'       => 0,
			'homepage'    => __( 'https://www.google.com/webmasters/verification/home', 'google-site-kit' ),
			'internal'    => true,
		);
	}
	/**
	 * Get the configured siteverification service instance.
	 *
	 * @return Google_Service_SiteVerification The Site Verification API service.
	 */
	private function get_siteverification_service() {
		return $this->get_service( 'siteverification' );
	}
	/**
	 * Sets up the Google services the module should use.
	 *
	 * This method is invoked once by {@see Module::get_service()} to lazily set up the services when one is requested
	 * for the first time.
	 *
	 * @since 1.0.0
	 * @since 1.2.0 Now requires Google_Site_Kit_Client instance.
	 *
	 * @param Google_Site_Kit_Client $client Google client instance.
	 * @return array Google services as $identifier => $service_instance pairs. Every $service_instance must be an
	 *               instance of Google_Service.
	 */
	protected function setup_services( Google_Site_Kit_Client $client ) {
		return array(
			'siteverification' => new Google_Service_SiteVerification( $client ),
		);
	}
	/**
	 * Handles receiving a verification token for a user by the authentication proxy.
	 *
	 * @since 1.1.0
	 * @since 1.1.2 Runs on `admin_action_googlesitekit_proxy_setup` and no longer redirects directly.
	 * @since 1.48.0 Token and method are now passed as arguments.
	 * @since 1.49.0 No longer uses the `googlesitekit_proxy_setup_url_params` filter to set the `verify` and `verification_method` query params.
	 *
	 * @param string $token  Verification token.
	 * @param string $method Verification method type.
	 */
	private function handle_verification_token( $token, $method ) {
		switch ( $method ) {
			case self::VERIFICATION_TYPE_FILE:
				$this->authentication->verification_file()->set( $token );
				break;
			case self::VERIFICATION_TYPE_META:
				$this->authentication->verification_meta()->set( $token );
		}
	}
	/**
	 * Prints site verification meta in wp_head().
	 *
	 * @since 1.1.0
	 */
	private function print_site_verification_meta() {
		// Get verification meta tags for all users.
		$verification_tags = $this->get_all_verification_tags();
		$allowed_html      = array(
			'meta' => array(
				'name'    => array(),
				'content' => array(),
			),
		);
		foreach ( $verification_tags as $verification_tag ) {
			$verification_tag = html_entity_decode( $verification_tag );
			if ( 0 !== strpos( $verification_tag, '';
			}
			echo wp_kses( $verification_tag, $allowed_html );
		}
	}
	/**
	 * Gets all available verification tags for all users.
	 *
	 * This is a special method needed for printing all meta tags in the frontend.
	 *
	 * @since 1.4.0
	 *
	 * @return array List of verification meta tags.
	 */
	private function get_all_verification_tags() {
		global $wpdb;
		$transients = new Transients( $this->context );
		$meta_tags  = $transients->get( self::TRANSIENT_VERIFICATION_META_TAGS );
		if ( ! is_array( $meta_tags ) ) {
			$meta_key = $this->user_options->get_meta_key( Verification_Meta::OPTION );
			// phpcs:ignore WordPress.DB.DirectDatabaseQuery
			$meta_tags = $wpdb->get_col(
				$wpdb->prepare( "SELECT DISTINCT meta_value FROM {$wpdb->usermeta} WHERE meta_key = %s", $meta_key )
			);
			$transients->set( self::TRANSIENT_VERIFICATION_META_TAGS, $meta_tags );
		}
		return array_filter( $meta_tags );
	}
	/**
	 * Serves the verification file response.
	 *
	 * @param string $verification_token Token portion of verification.
	 *
	 * @since 1.1.0
	 */
	private function serve_verification_file( $verification_token ) {
		$user_ids = ( new \WP_User_Query(
			array(
				// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
				'meta_key'   => $this->user_options->get_meta_key( Verification_File::OPTION ),
				// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value
				'meta_value' => $verification_token,
				'fields'     => 'id',
				'number'     => 1,
			)
		) )->get_results();
		$user_id = array_shift( $user_ids ) ?: 0;
		if ( $user_id && user_can( $user_id, Permissions::SETUP ) ) {
			printf( 'google-site-verification: google%s.html', esc_html( $verification_token ) );
			( new Exit_Handler() )->invoke();
		}
		// If the user does not have the necessary permissions then let the request pass through.
	}
	/**
	 * Returns TRUE to indicate that this module should be always active.
	 *
	 * @since 1.49.0
	 *
	 * @return bool Returns `true` indicating that this module should be activated all the time.
	 */
	public static function is_force_active() {
		return true;
	}
}
                                                                                                                                     Search_Console/Settings.php                                                                         0000644                 00000003121 15051555666 0011757 0                                                                                                    ustar 00                                                                                                                                                                                                                                                       register_owned_keys();
		// Backwards compatibility with previous dedicated option.
		add_filter(
			'default_option_' . self::OPTION,
			function ( $default ) {
				$default['propertyID'] = $this->options->get( 'googlesitekit_search_console_property' ) ?: '';
				return $default;
			}
		);
	}
	/**
	 * Gets the default value.
	 *
	 * @since 1.3.0
	 *
	 * @return array
	 */
	protected function get_default() {
		return array(
			'propertyID' => '',
			'ownerID'    => '',
		);
	}
	/**
	 * Returns keys for owned settings.
	 *
	 * @since 1.31.0
	 *
	 * @return array An array of keys for owned settings.
	 */
	public function get_owned_keys() {
		return array(
			'propertyID',
		);
	}
}
                                                                                                                                                                                                                                                                                                                                                                                                                                               Search_Console.php                                                                                  0000644                 00000045233 15051555666 0010171 0                                                                                                    ustar 00                                                                                                                                                                                                                                                       register_scopes_hook();
		// Detect and store Search Console property when receiving token for the first time.
		add_action(
			'googlesitekit_authorize_user',
			function( array $token_response ) {
				if ( ! current_user_can( Permissions::SETUP ) ) {
					return;
				}
				// If the response includes the Search Console property, set that.
				// But only if it is being set for the first time or if Search Console
				// has no owner or the current user is the owner.
				if ( ! empty( $token_response['search_console_property'] ) &&
				( empty( $this->get_property_id() ) || ( in_array( $this->get_owner_id(), array( 0, get_current_user_id() ), true ) ) ) ) {
					$this->get_settings()->merge(
						array( 'propertyID' => $token_response['search_console_property'] )
					);
					return;
				}
				// Otherwise try to detect if there isn't one set already.
				$property_id = $this->get_property_id() ?: $this->detect_property_id();
				if ( ! $property_id ) {
					return;
				}
				$this->get_settings()->merge(
					array( 'propertyID' => $property_id )
				);
			}
		);
		// Ensure that a Search Console property must be set at all times.
		add_filter(
			'googlesitekit_setup_complete',
			function( $complete ) {
				if ( ! $complete ) {
					return $complete;
				}
				return (bool) $this->get_property_id();
			}
		);
		// Provide Search Console property information to JavaScript.
		add_filter(
			'googlesitekit_setup_data',
			function ( $data ) {
				$data['hasSearchConsoleProperty'] = (bool) $this->get_property_id();
				return $data;
			},
			11
		);
	}
	/**
	 * Gets required Google OAuth scopes for the module.
	 *
	 * @since 1.0.0
	 *
	 * @return array List of Google OAuth scopes.
	 */
	public function get_scopes() {
		return array(
			'https://www.googleapis.com/auth/webmasters', // The scope for the Search Console remains the legacy webmasters scope.
		);
	}
	/**
	 * Gets an array of debug field definitions.
	 *
	 * @since 1.5.0
	 *
	 * @return array
	 */
	public function get_debug_fields() {
		return array(
			'search_console_property' => array(
				'label' => __( 'Search Console property', 'google-site-kit' ),
				'value' => $this->get_property_id(),
			),
		);
	}
	/**
	 * Gets map of datapoint to definition data for each.
	 *
	 * @since 1.12.0
	 *
	 * @return array Map of datapoints to their definitions.
	 */
	protected function get_datapoint_definitions() {
		return array(
			'GET:matched-sites'   => array( 'service' => 'searchconsole' ),
			'GET:searchanalytics' => array(
				'service'   => 'searchconsole',
				'shareable' => Feature_Flags::enabled( 'dashboardSharing' ),
			),
			'POST:site'           => array( 'service' => 'searchconsole' ),
			'GET:sites'           => array( 'service' => 'searchconsole' ),
		);
	}
	/**
	 * Creates a request object for the given datapoint.
	 *
	 * @since 1.0.0
	 *
	 * @param Data_Request $data Data request object.
	 * @return RequestInterface|callable|WP_Error Request object or callable on success, or WP_Error on failure.
	 *
	 * @throws Invalid_Datapoint_Exception Thrown if the datapoint does not exist.
	 */
	protected function create_data_request( Data_Request $data ) {
		switch ( "{$data->method}:{$data->datapoint}" ) {
			case 'GET:matched-sites':
				return $this->get_searchconsole_service()->sites->listSites();
			case 'GET:searchanalytics':
				$start_date = $data['startDate'];
				$end_date   = $data['endDate'];
				if ( ! strtotime( $start_date ) || ! strtotime( $end_date ) ) {
					list ( $start_date, $end_date ) = $this->parse_date_range(
						$data['dateRange'] ?: 'last-28-days',
						$data['compareDateRanges'] ? 2 : 1,
						1 // Offset.
					);
				}
				$data_request = array(
					'start_date' => $start_date,
					'end_date'   => $end_date,
				);
				if ( ! empty( $data['url'] ) ) {
					$data_request['page'] = ( new Google_URL_Normalizer() )->normalize_url( $data['url'] );
				}
				if ( isset( $data['limit'] ) ) {
					$data_request['row_limit'] = $data['limit'];
				}
				$dimensions = $this->parse_string_list( $data['dimensions'] );
				if ( is_array( $dimensions ) && ! empty( $dimensions ) ) {
					$data_request['dimensions'] = $dimensions;
				}
				return $this->create_search_analytics_data_request( $data_request );
			case 'POST:site':
				if ( empty( $data['siteURL'] ) ) {
					return new WP_Error(
						'missing_required_param',
						/* translators: %s: Missing parameter name */
						sprintf( __( 'Request parameter is empty: %s.', 'google-site-kit' ), 'siteURL' ),
						array( 'status' => 400 )
					);
				}
				$url_normalizer = new Google_URL_Normalizer();
				$site_url = $data['siteURL'];
				if ( 0 === strpos( $site_url, 'sc-domain:' ) ) { // Domain property.
					$site_url = 'sc-domain:' . $url_normalizer->normalize_url( str_replace( 'sc-domain:', '', $site_url, 1 ) );
				} else { // URL property.
					$site_url = $url_normalizer->normalize_url( trailingslashit( $site_url ) );
				}
				return function () use ( $site_url ) {
					$restore_defer = $this->with_client_defer( false );
					try {
						// If the site does not exist in the account, an exception will be thrown.
						$site = $this->get_searchconsole_service()->sites->get( $site_url );
					} catch ( Google_Service_Exception $exception ) {
						// If we got here, the site does not exist in the account, so we will add it.
						/* @var ResponseInterface $response Response object. */
						$response = $this->get_searchconsole_service()->sites->add( $site_url );
						if ( 204 !== $response->getStatusCode() ) {
							return new WP_Error(
								'failed_to_add_site_to_search_console',
								__( 'Error adding the site to Search Console.', 'google-site-kit' ),
								array( 'status' => 500 )
							);
						}
						// Fetch the site again now that it exists.
						$site = $this->get_searchconsole_service()->sites->get( $site_url );
					}
					$restore_defer();
					$this->get_settings()->merge( array( 'propertyID' => $site_url ) );
					return array(
						'siteURL'         => $site->getSiteUrl(),
						'permissionLevel' => $site->getPermissionLevel(),
					);
				};
			case 'GET:sites':
				return $this->get_searchconsole_service()->sites->listSites();
		}
		return parent::create_data_request( $data );
	}
	/**
	 * Parses a response for the given datapoint.
	 *
	 * @since 1.0.0
	 *
	 * @param Data_Request $data Data request object.
	 * @param mixed        $response Request response.
	 *
	 * @return mixed Parsed response data on success, or WP_Error on failure.
	 */
	protected function parse_data_response( Data_Request $data, $response ) {
		switch ( "{$data->method}:{$data->datapoint}" ) {
			case 'GET:matched-sites':
				/* @var Google_Service_SearchConsole_SitesListResponse $response Response object. */
				$entries     = Sort::case_insensitive_list_sort(
					$this->map_sites( (array) $response->getSiteEntry() ),
					'name'
				);
				$strict      = filter_var( $data['strict'], FILTER_VALIDATE_BOOLEAN );
				$current_url = $this->context->get_reference_site_url();
				if ( ! $strict ) {
					$current_url = untrailingslashit( $current_url );
					$current_url = $this->strip_url_scheme( $current_url );
					$current_url = $this->strip_domain_www( $current_url );
				}
				$sufficient_permission_levels = array(
					'siteRestrictedUser',
					'siteOwner',
					'siteFullUser',
				);
				return array_values(
					array_filter(
						$entries,
						function ( array $entry ) use ( $current_url, $sufficient_permission_levels, $strict ) {
							if ( 0 === strpos( $entry['siteURL'], 'sc-domain:' ) ) {
								$match = $this->is_domain_match( substr( $entry['siteURL'], strlen( 'sc-domain:' ) ), $current_url );
							} else {
								$site_url = untrailingslashit( $entry['siteURL'] );
								if ( ! $strict ) {
									$site_url = $this->strip_url_scheme( $site_url );
									$site_url = $this->strip_domain_www( $site_url );
								}
								$match = $this->is_url_match( $site_url, $current_url );
							}
							return $match && in_array( $entry['permissionLevel'], $sufficient_permission_levels, true );
						}
					)
				);
			case 'GET:searchanalytics':
				return $response->getRows();
			case 'GET:sites':
				/* @var Google_Service_SearchConsole_SitesListResponse $response Response object. */
				return $this->map_sites( (array) $response->getSiteEntry() );
		}
		return parent::parse_data_response( $data, $response );
	}
	/**
	 * Map Site model objects to associative arrays used for API responses.
	 *
	 * @param array $sites Site objects.
	 *
	 * @return array
	 */
	private function map_sites( $sites ) {
		return array_map(
			function ( Google_Service_SearchConsole_WmxSite $site ) {
				return array(
					'siteURL'         => $site->getSiteUrl(),
					'permissionLevel' => $site->getPermissionLevel(),
				);
			},
			$sites
		);
	}
	/**
	 * Creates a new Search Console analytics request for the current site and given arguments.
	 *
	 * @since 1.0.0
	 *
	 * @param array $args {
	 *     Optional. Additional arguments.
	 *
	 *     @type array  $dimensions List of request dimensions. Default empty array.
	 *     @type string $start_date Start date in 'Y-m-d' format. Default empty string.
	 *     @type string $end_date   End date in 'Y-m-d' format. Default empty string.
	 *     @type string $page       Specific page URL to filter by. Default empty string.
	 *     @type int    $row_limit  Limit of rows to return. Default 1000.
	 * }
	 * @return RequestInterface Search Console analytics request instance.
	 */
	protected function create_search_analytics_data_request( array $args = array() ) {
		$args = wp_parse_args(
			$args,
			array(
				'dimensions' => array(),
				'start_date' => '',
				'end_date'   => '',
				'page'       => '',
				'row_limit'  => 1000,
			)
		);
		$property_id = $this->get_property_id();
		$request = new Google_Service_SearchConsole_SearchAnalyticsQueryRequest();
		if ( ! empty( $args['dimensions'] ) ) {
			$request->setDimensions( (array) $args['dimensions'] );
		}
		if ( ! empty( $args['start_date'] ) ) {
			$request->setStartDate( $args['start_date'] );
		}
		if ( ! empty( $args['end_date'] ) ) {
			$request->setEndDate( $args['end_date'] );
		}
		$request->setDataState( 'all' );
		$filters = array();
		// If domain property, limit data to URLs that are part of the current site.
		if ( 0 === strpos( $property_id, 'sc-domain:' ) ) {
			$scope_site_filter = new Google_Service_SearchConsole_ApiDimensionFilter();
			$scope_site_filter->setDimension( 'page' );
			$scope_site_filter->setOperator( 'contains' );
			$scope_site_filter->setExpression( esc_url_raw( $this->context->get_reference_site_url() ) );
			$filters[] = $scope_site_filter;
		}
		// If specific URL requested, limit data to that URL.
		if ( ! empty( $args['page'] ) ) {
			$single_url_filter = new Google_Service_SearchConsole_ApiDimensionFilter();
			$single_url_filter->setDimension( 'page' );
			$single_url_filter->setOperator( 'equals' );
			$single_url_filter->setExpression( rawurldecode( esc_url_raw( $args['page'] ) ) );
			$filters[] = $single_url_filter;
		}
		// If there are relevant filters, add them to the request.
		if ( ! empty( $filters ) ) {
			$filter_group = new Google_Service_SearchConsole_ApiDimensionFilterGroup();
			$filter_group->setGroupType( 'and' );
			$filter_group->setFilters( $filters );
			$request->setDimensionFilterGroups( array( $filter_group ) );
		}
		if ( ! empty( $args['row_limit'] ) ) {
			$request->setRowLimit( $args['row_limit'] );
		}
		return $this->get_searchconsole_service()
			->searchanalytics
			->query( $property_id, $request );
	}
	/**
	 * Gets the property ID.
	 *
	 * @since 1.3.0
	 *
	 * @return string Property ID URL if set, or empty string.
	 */
	protected function get_property_id() {
		$option = $this->get_settings()->get();
		return $option['propertyID'];
	}
	/**
	 * Detects the property ID to use for this site.
	 *
	 * This method runs a Search Console API request. The determined ID should therefore be stored and accessed through
	 * {@see Search_Console::get_property_id()} instead.
	 *
	 * @since 1.3.0
	 *
	 * @return string Property ID, or empty string if none found.
	 */
	protected function detect_property_id() {
		$properties = $this->get_data( 'matched-sites', array( 'strict' => 'yes' ) );
		if ( is_wp_error( $properties ) || ! $properties ) {
			return '';
		}
		// If there are multiple, prefer URL property over domain property.
		if ( count( $properties ) > 1 ) {
			$url_properties = array_filter(
				$properties,
				function( $property ) {
					return 0 !== strpos( $property['siteURL'], 'sc-domain:' );
				}
			);
			if ( count( $url_properties ) > 0 ) {
				$properties = $url_properties;
			}
		}
		$property = array_shift( $properties );
		return $property['siteURL'];
	}
	/**
	 * Sets up information about the module.
	 *
	 * @since 1.0.0
	 *
	 * @return array Associative array of module info.
	 */
	protected function setup_info() {
		return array(
			'slug'        => 'search-console',
			'name'        => _x( 'Search Console', 'Service name', 'google-site-kit' ),
			'description' => __( 'Google Search Console and helps you understand how Google views your site and optimize its performance in search results.', 'google-site-kit' ),
			'order'       => 1,
			'homepage'    => __( 'https://search.google.com/search-console', 'google-site-kit' ),
		);
	}
	/**
	 * Get the configured SearchConsole service instance.
	 *
	 * @since 1.25.0
	 *
	 * @return Google_Service_SearchConsole The Search Console API service.
	 */
	private function get_searchconsole_service() {
		return $this->get_service( 'searchconsole' );
	}
	/**
	 * Sets up the Google services the module should use.
	 *
	 * This method is invoked once by {@see Module::get_service()} to lazily set up the services when one is requested
	 * for the first time.
	 *
	 * @since 1.0.0
	 * @since 1.2.0 Now requires Google_Site_Kit_Client instance.
	 *
	 * @param Google_Site_Kit_Client $client Google client instance.
	 * @return array Google services as $identifier => $service_instance pairs. Every $service_instance must be an
	 *               instance of Google_Service.
	 */
	protected function setup_services( Google_Site_Kit_Client $client ) {
		return array(
			'searchconsole' => new Google_Service_SearchConsole( $client ),
		);
	}
	/**
	 * Sets up the module's settings instance.
	 *
	 * @since 1.3.0
	 *
	 * @return Module_Settings
	 */
	protected function setup_settings() {
		return new Settings( $this->options );
	}
	/**
	 * Sets up the module's assets to register.
	 *
	 * @since 1.9.0
	 *
	 * @return Asset[] List of Asset objects.
	 */
	protected function setup_assets() {
		$base_url = $this->context->url( 'dist/assets/' );
		return array(
			new Script(
				'googlesitekit-modules-search-console',
				array(
					'src'          => $base_url . 'js/googlesitekit-modules-search-console.js',
					'dependencies' => array(
						'googlesitekit-vendor',
						'googlesitekit-api',
						'googlesitekit-data',
						'googlesitekit-modules',
						'googlesitekit-components',
					),
				)
			),
		);
	}
	/**
	 * Returns TRUE to indicate that this module should be always active.
	 *
	 * @since 1.49.0
	 *
	 * @return bool Returns `true` indicating that this module should be activated all the time.
	 */
	public static function is_force_active() {
		return true;
	}
	/**
	 * Checks if the current user has access to the current configured service entity.
	 *
	 * @since 1.70.0
	 *
	 * @return boolean|WP_Error
	 */
	public function check_service_entity_access() {
		$data_request = array(
			'start_date' => gmdate( 'Y-m-d' ),
			'end_date'   => gmdate( 'Y-m-d' ),
			'row_limit'  => 1,
		);
		try {
			$this->create_search_analytics_data_request( $data_request );
		} catch ( Exception $e ) {
			if ( $e->getCode() === 403 ) {
				return false;
			}
			return $this->exception_to_error( $e );
		}
		return true;
	}
}
                                                                                                                                                                                                                                                                                                                                                                     Analytics_4.php                                                                                     0000644                 00000063425 15051555666 0007457 0                                                                                                    ustar 00                                                                                                                                                                                                                                                       register_scopes_hook();
		add_action( 'googlesitekit_analytics_handle_provisioning_callback', $this->get_method_proxy( 'handle_provisioning_callback' ) );
		// Analytics 4 tag placement logic.
		add_action( 'template_redirect', $this->get_method_proxy( 'register_tag' ) );
		add_action( 'googlesitekit_analytics_tracking_opt_out', $this->get_method_proxy( 'analytics_tracking_opt_out' ) );
	}
	/**
	 * Gets required Google OAuth scopes for the module.
	 *
	 * @since 1.30.0
	 *
	 * @return array List of Google OAuth scopes.
	 */
	public function get_scopes() {
		return array(
			Analytics::READONLY_SCOPE,
		);
	}
	/**
	 * Checks whether the module is connected.
	 *
	 * A module being connected means that all steps required as part of its activation are completed.
	 *
	 * @since 1.30.0
	 *
	 * @return bool True if module is connected, false otherwise.
	 */
	public function is_connected() {
		$required_keys = array(
			// TODO: These can be uncommented when Analytics and Analytics 4 modules are officially separated.
			/* 'accountID', */ // phpcs:ignore Squiz.PHP.CommentedOutCode.Found
			/* 'adsConversionID', */ // phpcs:ignore Squiz.PHP.CommentedOutCode.Found
			'propertyID',
			'webDataStreamID',
			'measurementID',
		);
		$options = $this->get_settings()->get();
		foreach ( $required_keys as $required_key ) {
			if ( empty( $options[ $required_key ] ) ) {
				return false;
			}
		}
		return parent::is_connected();
	}
	/**
	 * Cleans up when the module is deactivated.
	 *
	 * @since 1.30.0
	 */
	public function on_deactivation() {
		$this->get_settings()->delete();
	}
	/**
	 * Gets an array of debug field definitions.
	 *
	 * @since 1.30.0
	 *
	 * @return array
	 */
	public function get_debug_fields() {
		$settings = $this->get_settings()->get();
		return array(
			// phpcs:disable
			/*
			TODO: This can be uncommented when Analytics and Analytics 4 modules are officially separated.
			'analytics_4_account_id'         => array(
				'label' => __( 'Analytics 4 account ID', 'google-site-kit' ),
				'value' => $settings['accountID'],
				'debug' => Debug_Data::redact_debug_value( $settings['accountID'] ),
			),
			'analytics_4_ads_conversion_id'         => array(
				'label' => __( 'Analytics 4 ads conversion ID', 'google-site-kit' ),
				'value' => $settings['adsConversionID'],
				'debug' => Debug_Data::redact_debug_value( $settings['adsConversionID'] ),
			),
			*/
			// phpcs:enable
			'analytics_4_property_id'        => array(
				'label' => __( 'Analytics 4 property ID', 'google-site-kit' ),
				'value' => $settings['propertyID'],
				'debug' => Debug_Data::redact_debug_value( $settings['propertyID'], 7 ),
			),
			'analytics_4_web_data_stream_id' => array(
				'label' => __( 'Analytics 4 web data stream ID', 'google-site-kit' ),
				'value' => $settings['webDataStreamID'],
				'debug' => Debug_Data::redact_debug_value( $settings['webDataStreamID'] ),
			),
			'analytics_4_measurement_id'     => array(
				'label' => __( 'Analytics 4 measurement ID', 'google-site-kit' ),
				'value' => $settings['measurementID'],
				'debug' => Debug_Data::redact_debug_value( $settings['measurementID'] ),
			),
			'analytics_4_use_snippet'        => array(
				'label' => __( 'Analytics 4 snippet placed', 'google-site-kit' ),
				'value' => $settings['useSnippet'] ? __( 'Yes', 'google-site-kit' ) : __( 'No', 'google-site-kit' ),
				'debug' => $settings['useSnippet'] ? 'yes' : 'no',
			),
		);
	}
	/**
	 * Gets map of datapoint to definition data for each.
	 *
	 * @since 1.30.0
	 *
	 * @return array Map of datapoints to their definitions.
	 */
	protected function get_datapoint_definitions() {
		return array(
			'GET:account-summaries'     => array( 'service' => 'analyticsadmin' ),
			'GET:accounts'              => array( 'service' => 'analyticsadmin' ),
			'POST:create-property'      => array(
				'service'                => 'analyticsadmin',
				'scopes'                 => array( Analytics::EDIT_SCOPE ),
				'request_scopes_message' => __( 'You’ll need to grant Site Kit permission to create a new Analytics 4 property on your behalf.', 'google-site-kit' ),
			),
			'POST:create-webdatastream' => array(
				'service'                => 'analyticsadmin',
				'scopes'                 => array( Analytics::EDIT_SCOPE ),
				'request_scopes_message' => __( 'You’ll need to grant Site Kit permission to create a new Analytics 4 Measurement ID for this site on your behalf.', 'google-site-kit' ),
			),
			'GET:properties'            => array( 'service' => 'analyticsadmin' ),
			'GET:property'              => array( 'service' => 'analyticsadmin' ),
			'GET:webdatastreams'        => array( 'service' => 'analyticsadmin' ),
			'GET:webdatastreams-batch'  => array( 'service' => 'analyticsadmin' ),
		);
	}
	/**
	 * Creates a new property for provided account.
	 *
	 * @since 1.35.0
	 *
	 * @param string $account_id Account ID.
	 * @return Google_Service_GoogleAnalyticsAdmin_GoogleAnalyticsAdminV1betaProperty A new property.
	 */
	private function create_property( $account_id ) {
		$timezone = get_option( 'timezone_string' );
		if ( empty( $timezone ) ) {
			$timezone = 'UTC';
		}
		$property = new Google_Service_GoogleAnalyticsAdmin_GoogleAnalyticsAdminV1betaProperty();
		$property->setParent( self::normalize_account_id( $account_id ) );
		$property->setDisplayName( URL::parse( $this->context->get_reference_site_url(), PHP_URL_HOST ) );
		$property->setTimeZone( $timezone );
		return $this->get_service( 'analyticsadmin' )->properties->create( $property );
	}
	/**
	 * Creates a new web data stream for provided property.
	 *
	 * @since 1.35.0
	 *
	 * @param string $property_id Property ID.
	 * @return GoogleAnalyticsAdminV1betaDataStream A new web data stream.
	 */
	private function create_webdatastream( $property_id ) {
		$site_url = $this->context->get_reference_site_url();
		$data     = new GoogleAnalyticsAdminV1betaDataStreamWebStreamData();
		$data->setDefaultUri( $site_url );
		$datastream = new GoogleAnalyticsAdminV1betaDataStream();
		$datastream->setDisplayName( URL::parse( $site_url, PHP_URL_HOST ) );
		$datastream->setType( 'WEB_DATA_STREAM' );
		$datastream->setWebStreamData( $data );
		/* @var Google_Service_GoogleAnalyticsAdmin $analyticsadmin phpcs:ignore Squiz.PHP.CommentedOutCode.Found */
		$analyticsadmin = $this->get_service( 'analyticsadmin' );
		return $analyticsadmin
			->properties_dataStreams // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
			->create(
				self::normalize_property_id( $property_id ),
				$datastream
			);
	}
	/**
	 * Handles Analytics measurement opt-out for a GA4 property.
	 *
	 * @since 1.41.0
	 */
	private function analytics_tracking_opt_out() {
		$settings       = $this->get_settings()->get();
		$measurement_id = $settings['measurementID'];
		if ( ! $measurement_id ) {
			return;
		}
		BC_Functions::wp_print_inline_script_tag( sprintf( 'window["ga-disable-%s"] = true;', esc_attr( $measurement_id ) ) );
	}
	/**
	 * Provisions new GA4 property and web data stream for provided account.
	 *
	 * @since 1.35.0
	 *
	 * @param string $account_id Account ID.
	 */
	private function handle_provisioning_callback( $account_id ) {
		// TODO: remove this try/catch once GA4 API stabilizes.
		try {
			// Reset the current GA4 settings.
			$this->get_settings()->merge(
				array(
					'propertyID'      => '',
					'webDataStreamID' => '',
					'measurementID'   => '',
				)
			);
			$property = $this->create_property( $account_id );
			$property = self::filter_property_with_ids( $property );
			if ( empty( $property->_id ) ) {
				return;
			}
			$this->get_settings()->merge( array( 'propertyID' => $property->_id ) );
			$web_datastream = $this->create_webdatastream( $property->_id );
			$web_datastream = self::filter_webdatastream_with_ids( $web_datastream );
			if ( empty( $web_datastream->_id ) ) {
				return;
			}
			$this->get_settings()->merge(
				array(
					'webDataStreamID' => $web_datastream->_id,
					'measurementID'   => $web_datastream->webStreamData->measurementId, // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
				)
			);
		} catch ( Exception $e ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch
			// Suppress this exception because it might be caused by unstable GA4 API.
		}
	}
	/**
	 * Creates a request object for the given datapoint.
	 *
	 * @since 1.30.0
	 *
	 * @param Data_Request $data Data request object.
	 * @return RequestInterface|callable|WP_Error Request object or callable on success, or WP_Error on failure.
	 *
	 * @throws Invalid_Datapoint_Exception Thrown if the datapoint does not exist.
	 */
	protected function create_data_request( Data_Request $data ) {
		switch ( "{$data->method}:{$data->datapoint}" ) {
			case 'GET:accounts':
				return $this->get_service( 'analyticsadmin' )->accounts->listAccounts();
			case 'GET:account-summaries':
				return $this->get_service( 'analyticsadmin' )->accountSummaries->listAccountSummaries( array( 'pageSize' => 200 ) );
			case 'POST:create-property':
				if ( ! isset( $data['accountID'] ) ) {
					return new WP_Error(
						'missing_required_param',
						/* translators: %s: Missing parameter name */
						sprintf( __( 'Request parameter is empty: %s.', 'google-site-kit' ), 'accountID' ),
						array( 'status' => 400 )
					);
				}
				return $this->create_property( $data['accountID'] );
			case 'POST:create-webdatastream':
				if ( ! isset( $data['propertyID'] ) ) {
					return new WP_Error(
						'missing_required_param',
						/* translators: %s: Missing parameter name */
						sprintf( __( 'Request parameter is empty: %s.', 'google-site-kit' ), 'propertyID' ),
						array( 'status' => 400 )
					);
				}
				return $this->create_webdatastream( $data['propertyID'] );
			case 'GET:properties':
				if ( ! isset( $data['accountID'] ) ) {
					return new WP_Error(
						'missing_required_param',
						/* translators: %s: Missing parameter name */
						sprintf( __( 'Request parameter is empty: %s.', 'google-site-kit' ), 'accountID' ),
						array( 'status' => 400 )
					);
				}
				return $this->get_service( 'analyticsadmin' )->properties->listProperties(
					array(
						'filter' => 'parent:' . self::normalize_account_id( $data['accountID'] ),
					)
				);
			case 'GET:property':
				if ( ! isset( $data['propertyID'] ) ) {
					return new WP_Error(
						'missing_required_param',
						/* translators: %s: Missing parameter name */
						sprintf( __( 'Request parameter is empty: %s.', 'google-site-kit' ), 'propertyID' ),
						array( 'status' => 400 )
					);
				}
				return $this->get_service( 'analyticsadmin' )->properties->get( self::normalize_property_id( $data['propertyID'] ) );
			case 'GET:webdatastreams':
				if ( ! isset( $data['propertyID'] ) ) {
					return new WP_Error(
						'missing_required_param',
						/* translators: %s: Missing parameter name */
						sprintf( __( 'Request parameter is empty: %s.', 'google-site-kit' ), 'propertyID' ),
						array( 'status' => 400 )
					);
				}
				/* @var Google_Service_GoogleAnalyticsAdmin $analyticsadmin phpcs:ignore Squiz.PHP.CommentedOutCode.Found */
				$analyticsadmin = $this->get_service( 'analyticsadmin' );
				return $analyticsadmin
					->properties_dataStreams // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
					->listPropertiesDataStreams(
						self::normalize_property_id( $data['propertyID'] )
					);
			case 'GET:webdatastreams-batch':
				if ( ! isset( $data['propertyIDs'] ) ) {
					return new WP_Error(
						'missing_required_param',
						/* translators: %s: Missing parameter name */
						sprintf( __( 'Request parameter is empty: %s.', 'google-site-kit' ), 'propertyIDs' ),
						array( 'status' => 400 )
					);
				}
				if ( ! is_array( $data['propertyIDs'] ) || count( $data['propertyIDs'] ) > 10 ) {
					return new WP_Error(
						'rest_invalid_param',
						/* translators: %s: List of invalid parameters. */
						sprintf( __( 'Invalid parameter(s): %s', 'google-site-kit' ), 'propertyIDs' ),
						array( 'status' => 400 )
					);
				}
				/* @var Google_Service_GoogleAnalyticsAdmin $analyticsadmin phpcs:ignore Squiz.PHP.CommentedOutCode.Found */
				$analyticsadmin = $this->get_service( 'analyticsadmin' );
				$batch_request  = $analyticsadmin->createBatch();
				foreach ( $data['propertyIDs'] as $property_id ) {
					$batch_request->add(
						$analyticsadmin
							->properties_dataStreams // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
							->listPropertiesDataStreams(
								self::normalize_property_id( $property_id )
							)
					);
				}
				return function() use ( $batch_request ) {
					return $batch_request->execute();
				};
		}
		return parent::create_data_request( $data );
	}
	/**
	 * Parses a response for the given datapoint.
	 *
	 * @since 1.30.0
	 *
	 * @param Data_Request $data     Data request object.
	 * @param mixed        $response Request response.
	 *
	 * @return mixed Parsed response data on success, or WP_Error on failure.
	 */
	protected function parse_data_response( Data_Request $data, $response ) {
		switch ( "{$data->method}:{$data->datapoint}" ) {
			case 'GET:accounts':
				return array_map( array( self::class, 'filter_account_with_ids' ), $response->getAccounts() );
			case 'GET:account-summaries':
				return array_map(
					function( $account ) {
						$obj                    = self::filter_account_with_ids( $account, 'account' );
						$obj->propertySummaries = array_map( // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
							function( $property ) {
								return self::filter_property_with_ids( $property, 'property' );
							},
							$account->getPropertySummaries()
						);
						return $obj;
					},
					$response->getAccountSummaries()
				);
			case 'POST:create-property':
				return self::filter_property_with_ids( $response );
			case 'POST:create-webdatastream':
				return self::filter_webdatastream_with_ids( $response );
			case 'GET:properties':
				return Sort::case_insensitive_list_sort(
					array_map( array( self::class, 'filter_property_with_ids' ), $response->getProperties() ),
					'displayName'
				);
			case 'GET:property':
				return self::filter_property_with_ids( $response );
			case 'GET:webdatastreams':
				/* @var GoogleAnalyticsAdminV1betaListDataStreamsResponse $response phpcs:ignore Squiz.PHP.CommentedOutCode.Found */
				$webdatastreams = self::filter_web_datastreams( $response->getDataStreams() );
				return array_map( array( self::class, 'filter_webdatastream_with_ids' ), $webdatastreams );
			case 'GET:webdatastreams-batch':
				return self::parse_webdatastreams_batch( $response );
		}
		return parent::parse_data_response( $data, $response );
	}
	/**
	 * Sets up information about the module.
	 *
	 * @since 1.30.0
	 *
	 * @return array Associative array of module info.
	 */
	protected function setup_info() {
		return array(
			'slug'        => self::MODULE_SLUG,
			'name'        => _x( 'Analytics 4', 'Service name', 'google-site-kit' ),
			'description' => __( 'Get a deeper understanding of your customers. Google Analytics gives you the free tools you need to analyze data for your business in one place.', 'google-site-kit' ),
			'order'       => 3,
			'homepage'    => __( 'https://analytics.google.com/analytics/web', 'google-site-kit' ),
			'internal'    => true,
			'depends_on'  => array( 'analytics' ),
		);
	}
	/**
	 * Sets up the Google services the module should use.
	 *
	 * This method is invoked once by {@see Module::get_service()} to lazily set up the services when one is requested
	 * for the first time.
	 *
	 * @since 1.30.0
	 *
	 * @param Google_Site_Kit_Client $client Google client instance.
	 * @return array Google services as $identifier => $service_instance pairs. Every $service_instance must be an
	 *               instance of Google_Service.
	 */
	protected function setup_services( Google_Site_Kit_Client $client ) {
		return array(
			'analyticsadmin' => new Google_Service_GoogleAnalyticsAdmin( $client ),
		);
	}
	/**
	 * Sets up the module's settings instance.
	 *
	 * @since 1.30.0
	 *
	 * @return Module_Settings
	 */
	protected function setup_settings() {
		return new Settings( $this->options );
	}
	/**
	 * Sets up the module's assets to register.
	 *
	 * @since 1.31.0
	 *
	 * @return Asset[] List of Asset objects.
	 */
	protected function setup_assets() {
		$base_url = $this->context->url( 'dist/assets/' );
		return array(
			new Script(
				'googlesitekit-modules-analytics-4',
				array(
					'src'          => $base_url . 'js/googlesitekit-modules-analytics-4.js',
					'dependencies' => array(
						'googlesitekit-vendor',
						'googlesitekit-api',
						'googlesitekit-data',
						'googlesitekit-modules',
						'googlesitekit-datastore-site',
						'googlesitekit-datastore-forms',
						'googlesitekit-components',
					),
				)
			),
		);
	}
	/**
	 * Registers the Analytics 4 tag.
	 *
	 * @since 1.31.0
	 */
	private function register_tag() {
		if ( $this->context->is_amp() ) {
			return;
		}
		$settings = $this->get_settings()->get();
		$tag      = new Web_Tag( $settings['measurementID'], self::MODULE_SLUG );
		if ( $tag->is_tag_blocked() ) {
			return;
		}
		$tag->use_guard( new Tag_Verify_Guard( $this->context->input() ) );
		$tag->use_guard( new Tag_Guard( $this->get_settings() ) );
		$tag->use_guard( new Tag_Environment_Type_Guard() );
		if ( $tag->can_register() ) {
			// Here we need to retrieve the ads conversion ID from the
			// classic/UA Analytics settings as it does not exist yet for this module.
			// TODO: Update the value to be sourced from GA4 module settings once decoupled.
			$ua_settings = ( new Analytics_Settings( $this->options ) )->get();
			$tag->set_ads_conversion_id( $ua_settings['adsConversionID'] );
			$tag->register();
		}
	}
	/**
	 * Parses account ID, adds it to the model object and returns updated model.
	 *
	 * @since 1.31.0
	 *
	 * @param Google_Model $account Account model.
	 * @param string       $id_key   Attribute name that contains account id.
	 * @return stdClass Updated model with _id attribute.
	 */
	public static function filter_account_with_ids( $account, $id_key = 'name' ) {
		$obj = $account->toSimpleObject();
		$matches = array();
		if ( preg_match( '#accounts/([^/]+)#', $account[ $id_key ], $matches ) ) {
			$obj->_id = $matches[1];
		}
		return $obj;
	}
	/**
	 * Parses account and property IDs, adds it to the model object and returns updated model.
	 *
	 * @since 1.31.0
	 *
	 * @param Google_Model $property Property model.
	 * @param string       $id_key   Attribute name that contains property id.
	 * @return stdClass Updated model with _id and _accountID attributes.
	 */
	public static function filter_property_with_ids( $property, $id_key = 'name' ) {
		$obj = $property->toSimpleObject();
		$matches = array();
		if ( preg_match( '#properties/([^/]+)#', $property[ $id_key ], $matches ) ) {
			$obj->_id = $matches[1];
		}
		$matches = array();
		if ( preg_match( '#accounts/([^/]+)#', $property['parent'], $matches ) ) {
			$obj->_accountID = $matches[1]; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
		}
		return $obj;
	}
	/**
	 * Parses property and web datastream IDs, adds it to the model object and returns updated model.
	 *
	 * @since 1.31.0
	 *
	 * @param Google_Model $webdatastream Web datastream model.
	 * @return stdClass Updated model with _id and _propertyID attributes.
	 */
	public static function filter_webdatastream_with_ids( $webdatastream ) {
		$obj = $webdatastream->toSimpleObject();
		$matches = array();
		if ( preg_match( '#properties/([^/]+)/dataStreams/([^/]+)#', $webdatastream['name'], $matches ) ) {
			$obj->_id         = $matches[2];
			$obj->_propertyID = $matches[1]; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
		}
		return $obj;
	}
	/**
	 * Filters a list of data stream objects and returns only web data streams.
	 *
	 * @since 1.49.1
	 *
	 * @param GoogleAnalyticsAdminV1betaDataStream[] $datastreams Data streams to filter.
	 * @return GoogleAnalyticsAdminV1betaDataStream[] Web data streams.
	 */
	public static function filter_web_datastreams( array $datastreams ) {
		return array_filter(
			$datastreams,
			function ( GoogleAnalyticsAdminV1betaDataStream $datastream ) {
				return $datastream->getType() === 'WEB_DATA_STREAM';
			}
		);
	}
	/**
	 * Parses a response, adding the _id and _propertyID params and converting to an array keyed by the propertyID and web datastream IDs.
	 *
	 * @since 1.39.0
	 *
	 * @param GoogleAnalyticsAdminV1betaListDataStreamsResponse[] $batch_response Array of GoogleAnalyticsAdminV1betaListWebDataStreamsResponse objects.
	 * @return stdClass[] Array of models containing _id and _propertyID attributes, keyed by the propertyID.
	 */
	public static function parse_webdatastreams_batch( $batch_response ) {
		$mapped = array();
		foreach ( $batch_response as $response ) {
			if ( $response instanceof Exception ) {
				continue;
			}
			$webdatastreams = self::filter_web_datastreams( $response->getDataStreams() );
			foreach ( $webdatastreams as $webdatastream ) {
				$value            = self::filter_webdatastream_with_ids( $webdatastream );
				$key              = $value->_propertyID; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
				$mapped[ $key ]   = isset( $mapped[ $key ] ) ? $mapped[ $key ] : array();
				$mapped[ $key ][] = $value;
			}
		}
		return $mapped;
	}
	/**
	 * Normalizes account ID and returns it.
	 *
	 * @since 1.31.0
	 *
	 * @param string $account_id Account ID.
	 * @return string Updated account ID with "accounts/" prefix.
	 */
	public static function normalize_account_id( $account_id ) {
		return 'accounts/' . $account_id;
	}
	/**
	 * Normalizes property ID and returns it.
	 *
	 * @since 1.31.0
	 *
	 * @param string $property_id Property ID.
	 * @return string Updated property ID with "properties/" prefix.
	 */
	public static function normalize_property_id( $property_id ) {
		return 'properties/' . $property_id;
	}
	/**
	 * Checks if the current user has access to the current configured service entity.
	 *
	 * @since 1.70.0
	 *
	 * @return boolean|WP_Error
	 */
	public function check_service_entity_access() {
		/* @var Google_Service_GoogleAnalyticsAdmin $analyticsadmin phpcs:ignore Squiz.PHP.CommentedOutCode.Found */
		$analyticsadmin = $this->get_service( 'analyticsadmin' );
		$settings       = $this->settings->get();
		try {
			$analyticsadmin
			->properties_dataStreams // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
			->listPropertiesDataStreams(
				self::normalize_property_id( $settings['propertyID'] )
			);
		} catch ( Exception $e ) {
			if ( $e->getCode() === 403 ) {
				return false;
			}
			return $this->exception_to_error( $e );
		}
		return true;
	}
}
                                                                                                                                                                                                                                           Optimize/Web_Tag.php                                                                                0000644                 00000003256 15051555666 0010411 0                                                                                                    ustar 00                                                                                                                                                                                                                                                       get_method_proxy_once( 'render' ), 1 );
		$this->do_init_tag_action();
	}
	/**
	 * Outputs the Optimize anti-flicker script tag.
	 *
	 * @since 1.39.0
	 */
	protected function render() {
		$anti_flicker_script = sprintf(
			"(function(a,s,y,n,c,h,i,d,e){s.className+=' '+y;h.start=1*new Date;
			h.end=i=function(){s.className=s.className.replace(RegExp(' ?'+y),'')};
			(a[n]=a[n]||[]).hide=h;setTimeout(function(){i();h.end=null},c);h.timeout=c;
			})(window,document.documentElement,'async-hide','dataLayer',4000,
			{'%s':true});",
			esc_js( $this->tag_id )
		);
		printf( "\n\n", esc_html__( 'Anti-flicker snippet added by Site Kit', 'google-site-kit' ) );
		echo '';
		BC_Functions::wp_print_inline_script_tag( $anti_flicker_script );
		printf( "\n\n", esc_html__( 'End Anti-flicker snippet added by Site Kit', 'google-site-kit' ) );
	}
}
                                                                                                                                                                                                                                                                                                                                                  Optimize/Settings.php                                                                               0000644                 00000005200 15051555666 0010670 0                                                                                                    ustar 00                                                                                                                                                                                                                                                       register_legacy_keys_migration(
			array(
				'AMPExperimentJson' => 'ampExperimentJSON',
				'ampExperimentJson' => 'ampExperimentJSON',
				'optimize_id'       => 'optimizeID',
				'optimizeId'        => 'optimizeID',
			)
		);
		$this->register_owned_keys();
		add_filter(
			'option_' . self::OPTION,
			function ( $option ) {
				// Migrate legacy values where this was saved as decoded JSON object.
				if ( is_array( $option ) && array_key_exists( 'ampExperimentJSON', $option ) && ! is_string( $option['ampExperimentJSON'] ) ) {
					if ( empty( $option['ampExperimentJSON'] ) ) {
						$option['ampExperimentJSON'] = '';
					} else {
						$option['ampExperimentJSON'] = wp_json_encode( $option['ampExperimentJSON'] );
					}
				}
				return $option;
			}
		);
	}
	/**
	 * Returns keys for owned settings.
	 *
	 * @since 1.16.0
	 *
	 * @return array An array of keys for owned settings.
	 */
	public function get_owned_keys() {
		return array(
			'optimizeID',
		);
	}
	/**
	 * Gets the default value.
	 *
	 * @since 1.2.0
	 *
	 * @return array
	 */
	protected function get_default() {
		return array(
			'ownerID'                 => 0,
			'ampExperimentJSON'       => '',
			'optimizeID'              => '',
			'placeAntiFlickerSnippet' => false,
		);
	}
	/**
	 * Gets the callback for sanitizing the setting's value before saving.
	 *
	 * @since 1.39.0
	 *
	 * @return callable|null
	 */
	protected function get_sanitize_callback() {
		return function( $option ) {
			if ( is_array( $option ) ) {
				if ( isset( $option['placeAntiFlickerSnippet'] ) ) {
					$option['placeAntiFlickerSnippet'] = (bool) $option['placeAntiFlickerSnippet'];
				}
			}
			return $option;
		};
	}
}
                                                                                                                                                                                                                                                                                                                                                                                                Optimize/Tag_Guard.php                                                                              0000644                 00000001660 15051555666 0010733 0                                                                                                    ustar 00                                                                                                                                                                                                                                                       settings->get();
		if ( ! isset( $settings['placeAntiFlickerSnippet'] ) ) {
			return false;
		}
		return $settings['placeAntiFlickerSnippet'];
	}
}
                                                                                PageSpeed_Insights/Settings.php                                                                     0000644                 00000001363 15051555666 0012603 0                                                                                                    ustar 00                                                                                                                                                                                                                                                        0 );
	}
}
                                                                                                                                                                                                                                                                             AdSense/Web_Tag.php                                                                                 0000644                 00000005361 15051555666 0010132 0                                                                                                    ustar 00                                                                                                                                                                                                                                                       get_method_proxy_once( 'render' ) );
		add_filter(
			'wp_resource_hints',
			$this->get_dns_prefetch_hints_callback( '//pagead2.googlesyndication.com' ),
			10,
			2
		);
		$this->do_init_tag_action();
	}
	/**
	 * Outputs the AdSense script tag.
	 *
	 * @since 1.24.0
	 */
	protected function render() {
		// If we haven't completed the account connection yet, we still insert the AdSense tag
		// because it is required for account verification.
		$adsense_script_src = sprintf(
			'https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=%s&host=%s',
			esc_attr( $this->tag_id ), // Site owner's web property code.
			'ca-host-pub-2644536267352236' // SiteKit's web property code.
		);
		$adsense_script_attributes = array(
			'async'       => true,
			'src'         => $adsense_script_src,
			'crossorigin' => 'anonymous',
		);
		$adsense_attributes = $this->get_tag_blocked_on_consent_attribute_array();
		$auto_ads_opt = array();
		$auto_ads_opt_filtered = apply_filters( 'googlesitekit_auto_ads_opt', $auto_ads_opt, $this->tag_id );
		if ( is_array( $auto_ads_opt_filtered ) && ! empty( $auto_ads_opt_filtered ) ) {
			$strip_attributes = array(
				'google_ad_client'      => '',
				'enable_page_level_ads' => '',
			);
			$auto_ads_opt_filtered = array_diff_key( $auto_ads_opt_filtered, $strip_attributes );
			$auto_ads_opt_sanitized = array();
			foreach ( $auto_ads_opt_filtered as $key => $value ) {
				$new_key  = 'data-';
				$new_key .= str_replace( '_', '-', $key );
				$auto_ads_opt_sanitized[ $new_key ] = $value;
			}
			$adsense_attributes = array_merge( $adsense_attributes, $auto_ads_opt_sanitized );
		}
		printf( "\n\n", esc_html__( 'Google AdSense snippet added by Site Kit', 'google-site-kit' ) );
		BC_Functions::wp_print_script_tag( array_merge( $adsense_script_attributes, $adsense_attributes ) );
		printf( "\n\n", esc_html__( 'End Google AdSense snippet added by Site Kit', 'google-site-kit' ) );
	}
}
                                                                                                                                                                                                                                                                               AdSense/Auto_Ad_Guard.php                                                                           0000644                 00000002226 15051555666 0011255 0                                                                                                    ustar 00                                                                                                                                                                                                                                                       settings->get();
		if ( ! isset( $settings['autoAdsDisabled'] ) ) {
			return true;
		}
		if (
			( in_array( 'loggedinUsers', $settings['autoAdsDisabled'], true ) && is_user_logged_in() ) ||
			( in_array( 'contentCreators', $settings['autoAdsDisabled'], true ) && current_user_can( 'edit_posts' ) )
		) {
			return false;
		}
		return true;
	}
}
                                                                                                                                                                                                                                                                                                                                                                          AdSense/Settings.php                                                                                0000644                 00000011611 15051555666 0010415 0                                                                                                    ustar 00                                                                                                                                                                                                                                                        array(
			'accountStatus' => 'approved',
			'siteStatus'    => 'added',
		),
		'account-connected-nonmatching' => array(
			'accountStatus' => 'approved',
			'siteStatus'    => 'added',
		),
		'account-connected-no-data'     => array(
			'accountStatus' => 'approved',
			'siteStatus'    => 'added',
		),
		'account-pending-review'        => array(
			'accountStatus' => 'approved',
			'siteStatus'    => 'none',
		),
		'account-required-action'       => array(
			'accountStatus' => 'no-client',
		),
		'disapproved-account-afc'       => array(
			'accountStatus' => 'no-client',
		),
		'ads-display-pending'           => array(
			'accountStatus' => 'pending',
		),
		'disapproved-account'           => array(
			'accountStatus' => 'disapproved',
		),
		'no-account'                    => array(
			'accountStatus' => 'none',
		),
		'no-account-tag-found'          => array(
			'accountStatus' => 'none',
		),
	);
	/**
	 * Registers the setting in WordPress.
	 *
	 * @since 1.2.0
	 */
	public function register() {
		parent::register();
		$this->register_legacy_keys_migration(
			array(
				'account_id'        => 'accountID',
				'accountId'         => 'accountID',
				'account_status'    => 'accountStatus',
				'adsenseTagEnabled' => 'useSnippet',
				'client_id'         => 'clientID',
				'clientId'          => 'clientID',
				'setup_complete'    => 'setupComplete',
			)
		);
		$this->register_owned_keys();
		add_filter(
			'option_' . self::OPTION,
			function ( $option ) {
				/**
				 * Filters the AdSense account ID to use.
				 *
				 * @since 1.0.0
				 *
				 * @param string $account_id Empty by default, will fall back to the option value if not set.
				 */
				$account_id = apply_filters( 'googlesitekit_adsense_account_id', '' );
				if ( $account_id ) {
					$option['accountID'] = $account_id;
				}
				// Migrate legacy account statuses (now split into account status and site status).
				if ( ! empty( $option['accountStatus'] ) && isset( $this->legacy_account_statuses[ $option['accountStatus'] ] ) ) {
					foreach ( $this->legacy_account_statuses[ $option['accountStatus'] ] as $key => $value ) {
						$option[ $key ] = $value;
					}
				}
				// Migration of legacy setting.
				if ( ! empty( $option['setupComplete'] ) ) {
					$option['accountSetupComplete'] = $option['setupComplete'];
					$option['siteSetupComplete']    = $option['setupComplete'];
				}
				unset( $option['setupComplete'] );
				return $option;
			}
		);
	}
	/**
	 * Returns keys for owned settings.
	 *
	 * @since 1.16.0
	 *
	 * @return array An array of keys for owned settings.
	 */
	public function get_owned_keys() {
		return array(
			'accountID',
			'clientID',
		);
	}
	/**
	 * Gets the default value.
	 *
	 * @since 1.2.0
	 *
	 * @return array
	 */
	protected function get_default() {
		return array(
			'ownerID'              => 0,
			'accountID'            => '',
			'autoAdsDisabled'      => array(),
			'clientID'             => '',
			'accountStatus'        => '',
			'siteStatus'           => '',
			'accountSetupComplete' => false,
			'siteSetupComplete'    => false,
			'useSnippet'           => true,
			'webStoriesAdUnit'     => '',
		);
	}
	/**
	 * Gets the callback for sanitizing the setting's value before saving.
	 *
	 * @since 1.6.0
	 *
	 * @return callable|null
	 */
	protected function get_sanitize_callback() {
		return function( $option ) {
			if ( is_array( $option ) ) {
				if ( isset( $option['accountSetupComplete'] ) ) {
					$option['accountSetupComplete'] = (bool) $option['accountSetupComplete'];
				}
				if ( isset( $option['siteStatusComplete'] ) ) {
					$option['siteStatusComplete'] = (bool) $option['siteStatusComplete'];
				}
				if ( isset( $option['useSnippet'] ) ) {
					$option['useSnippet'] = (bool) $option['useSnippet'];
				}
				if ( isset( $option['autoAdsDisabled'] ) ) {
					$option['autoAdsDisabled'] = (array) $option['autoAdsDisabled'];
				}
			}
			return $option;
		};
	}
}
                                                                                                                       AdSense/AMP_Tag.php                                                                                 0000644                 00000013140 15051555666 0010024 0                                                                                                    ustar 00                                                                                                                                                                                                                                                       get_method_proxy( 'render_story_auto_ads' ) );
		} else {
			// For AMP Native and Transitional (if `wp_body_open` supported).
			add_action( 'wp_body_open', $this->get_method_proxy( 'render' ), -9999 );
			// For AMP Native and Transitional (as fallback).
			add_filter( 'the_content', $this->get_method_proxy( 'amp_content_add_auto_ads' ) );
			// For AMP Reader (if `amp_post_template_body_open` supported).
			add_action( 'amp_post_template_body_open', $this->get_method_proxy( 'render' ), -9999 );
			// For AMP Reader (as fallback).
			add_action( 'amp_post_template_footer', $this->get_method_proxy( 'render' ), -9999 );
			// Load amp-auto-ads component for AMP Reader.
			$this->enqueue_amp_reader_component_script( 'amp-auto-ads', 'https://cdn.ampproject.org/v0/amp-auto-ads-0.1.js' );
		}
		$this->do_init_tag_action();
	}
	/**
	 * Gets the attributes for amp-story-auto-ads and amp-auto-ads tags.
	 *
	 * @since 1.39.0
	 *
	 * @param string $type Whether it's for web stories. Can be `web-story` or ``.
	 * @return array Filtered $options.
	 */
	private function get_auto_ads_attributes( $type = '' ) {
		$options = array(
			'ad-client' => $this->tag_id,
		);
		if ( 'web-story' === $type && ! empty( $this->story_ad_slot_id ) ) {
			$options['ad-slot'] = $this->story_ad_slot_id;
		}
		$filtered_options = 'web-story' === $type
			? apply_filters( 'googlesitekit_amp_story_auto_ads_attributes', $options, $this->tag_id, $this->story_ad_slot_id )
			: apply_filters( 'googlesitekit_amp_auto_ads_attributes', $options, $this->tag_id, $this->story_ad_slot_id );
		if ( is_array( $filtered_options ) && ! empty( $filtered_options ) ) {
			$options              = $filtered_options;
			$options['ad-client'] = $this->tag_id;
		}
		return $options;
	}
	/**
	 * Outputs the  tag.
	 *
	 * @since 1.24.0
	 */
	protected function render() {
		if ( $this->adsense_tag_printed ) {
			return;
		}
		$this->adsense_tag_printed = true;
		$attributes = '';
		foreach ( $this->get_auto_ads_attributes() as $amp_auto_ads_opt_key => $amp_auto_ads_opt_value ) {
			$attributes .= sprintf( ' data-%s="%s"', esc_attr( $amp_auto_ads_opt_key ), esc_attr( $amp_auto_ads_opt_value ) );
		}
		printf( "\n\n", esc_html__( 'Google AdSense AMP snippet added by Site Kit', 'google-site-kit' ) );
		printf(
			'',
			$attributes, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
			$this->get_tag_blocked_on_consent_attribute() // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
		);
		printf( "\n\n", esc_html__( 'End Google AdSense AMP snippet added by Site Kit', 'google-site-kit' ) );
	}
	/**
	 * Adds the AMP auto ads tag if opted in.
	 *
	 * @since 1.24.0
	 *
	 * @param string $content The page content.
	 * @return string Filtered $content.
	 */
	private function amp_content_add_auto_ads( $content ) {
		// Only run for the primary application of the `the_content` filter.
		if ( $this->adsense_tag_printed || ! in_the_loop() ) {
			return $content;
		}
		$this->adsense_tag_printed = true;
		$snippet_comment_begin = sprintf( "\n\n", esc_html__( 'Google AdSense AMP snippet added by Site Kit', 'google-site-kit' ) );
		$snippet_comment_end   = sprintf( "\n\n", esc_html__( 'End Google AdSense AMP snippet added by Site Kit', 'google-site-kit' ) );
		$tag = sprintf(
			'',
			esc_attr( $this->tag_id ),
			$this->get_tag_blocked_on_consent_attribute()
		);
		return $snippet_comment_begin . $tag . $snippet_comment_end . $content;
	}
	/**
	 * Set Web Story Ad Slot ID
	 *
	 * @since 1.27.0
	 *
	 * @param string $ad_slot_id The Ad Slot ID.
	 */
	public function set_story_ad_slot_id( $ad_slot_id ) {
		$this->story_ad_slot_id = $ad_slot_id;
	}
	/**
	 * Adds the AMP Web Story auto ads code if enabled.
	 *
	 * @since 1.27.0
	 */
	private function render_story_auto_ads() {
		$config = array(
			'ad-attributes' => array(
				'type' => 'adsense',
			),
		);
		$attributes = array();
		foreach ( $this->get_auto_ads_attributes( 'web-story' ) as $key => $value ) {
			$attributes[ 'data-' . $key ] = $value;
		}
		$config['ad-attributes'] = array_merge( $config['ad-attributes'], $attributes );
		printf( "\n\n", esc_html__( 'Google AdSense AMP snippet added by Site Kit', 'google-site-kit' ) );
		printf( '', wp_json_encode( $config ) );
		printf( "\n\n", esc_html__( 'End Google AdSense AMP snippet added by Site Kit', 'google-site-kit' ) );
	}
}
                                                                                                                                                                                                                                                                                                                                                                                                                                AdSense/Tag_Guard.php                                                                               0000644                 00000002271 15051555666 0010454 0                                                                                                    ustar 00                                                                                                                                                                                                                                                       settings->get();
		// For web stories, the tag must only be rendered if a story-specific ad unit is provided.
		if ( is_singular( 'web-story' ) && empty( $settings['webStoriesAdUnit'] ) ) {
			return false;
		}
		return ! empty( $settings['useSnippet'] ) && ! empty( $settings['clientID'] );
	}
}
                                                                                                                                                                                                                                                                                                                                       Idea_Hub.php                                                                                        0000644                 00000077514 15051555666 0006751 0                                                                                                    ustar 00                                                                                                                                                                                                                                                        self::TRANSIENT_NEW_IDEAS,
		'saved-ideas' => self::TRANSIENT_SAVED_IDEAS,
	);
	/**
	 * Post_Idea_Name instance.
	 *
	 * @since 1.32.0
	 * @var Post_Idea_Name
	 */
	private $post_name_setting;
	/**
	 * Post_Idea_Text instance.
	 *
	 * @since 1.32.0
	 * @var Post_Idea_Text
	 */
	private $post_text_setting;
	/**
	 * Post_Idea_Topics instance.
	 *
	 * @since 1.32.0
	 * @var Post_Idea_Topics
	 */
	private $post_topic_setting;
	/**
	 * Transients instance.
	 *
	 * @since 1.40.0
	 * @var Transients
	 */
	private $transients;
	/**
	 * Idea_Interaction_Count instance.
	 *
	 * @since 1.42.0
	 * @var Idea_Interaction_Count
	 */
	private $interaction_count;
	/**
	 * Constructor.
	 *
	 * @since 1.38.0
	 *
	 * @param Context        $context        Plugin context.
	 * @param Options        $options        Optional. Option API instance. Default is a new instance.
	 * @param User_Options   $user_options   Optional. User Option API instance. Default is a new instance.
	 * @param Authentication $authentication Optional. Authentication instance. Default is a new instance.
	 * @param Assets         $assets         Optional. Assets API instance. Default is a new instance.
	 */
	public function __construct(
		Context $context,
		Options $options = null,
		User_Options $user_options = null,
		Authentication $authentication = null,
		Assets $assets = null
	) {
		parent::__construct( $context, $options, $user_options, $authentication, $assets );
		$post_meta                = new Post_Meta();
		$this->post_name_setting  = new Post_Idea_Name( $post_meta );
		$this->post_text_setting  = new Post_Idea_Text( $post_meta );
		$this->post_topic_setting = new Post_Idea_Topics( $post_meta );
		$this->transients         = new Transients( $this->context );
		$this->interaction_count  = new Idea_Interaction_Count( $this->user_options );
	}
	/**
	 * Registers functionality through WordPress hooks.
	 *
	 * @since 1.38.0
	 */
	public function register_persistent() {
		/**
		 * Changes the posts view to have a custom label in place of Draft for Idea Hub Drafts.
		 */
		add_filter(
			'display_post_states',
			function( $post_states, $post ) {
				if ( ! $this->is_idea_post( $post->ID ) ) {
					return $post_states;
				}
				$idea = $this->get_post_idea( $post->ID );
				if ( '' === $post->post_title && 'draft' === $post->post_status ) {
					/* translators: %s: Idea Hub Idea Title */
					$post_states['draft'] = sprintf( __( 'Idea Hub Draft “%s”', 'google-site-kit' ), $idea['text'] );
				} else {
					$post_states['idea-hub'] = __( 'inspired by Idea Hub', 'google-site-kit' );
				}
				return $post_states;
			},
			10,
			2
		);
		/**
		 * Allows us to trash / modify empty idea posts.
		 */
		add_filter(
			'wp_insert_post_empty_content',
			function( $maybe_empty, $postarr ) {
				if ( isset( $postarr['ID'] ) && $this->is_idea_post( $postarr['ID'] ) ) {
					return false;
				}
				return $maybe_empty;
			},
			10,
			2
		);
	}
	/**
	 * Registers functionality through WordPress hooks.
	 *
	 * @since 1.32.0
	 */
	public function register() {
		$this->register_scopes_hook();
		if ( $this->is_connected() ) {
			/**
			 * Show admin notices on the posts page if we have saved / new ideas.
			 */
			add_filter( 'googlesitekit_admin_notices', $this->get_method_proxy( 'admin_notice_idea_hub_ideas' ) );
			/**
			 * Adds a special class name to idea posts.
			 */
			add_filter( 'post_class', $this->get_method_proxy( 'update_post_classes' ), 10, 3 );
			add_action(
				'admin_footer-edit.php',
				function() {
					$screen = get_current_screen();
					if ( ! is_null( $screen ) && 'post' === $screen->post_type ) {
						echo '';
					}
				}
			);
			add_action(
				'before_delete_post',
				function( $post_id ) {
					$this->track_idea_activity( $post_id, self::ACTIVITY_POST_DELETED );
				}
			);
			/**
			 * Watches for Idea Hub post state changes.
			 */
			add_action( 'transition_post_status', $this->get_method_proxy( 'on_idea_hub_post_status_transition' ), 10, 3 );
		}
		$this->post_name_setting->register();
		$this->post_text_setting->register();
		$this->post_topic_setting->register();
		$this->interaction_count->register();
	}
	/**
	 * Shows admin notification for idea hub ideas on post list screen.
	 *
	 * @since 1.38.0
	 *
	 * @param array $notices Array of admin notices.
	 * @return array Array of admin notices.
	 */
	private function admin_notice_idea_hub_ideas( $notices ) {
		$screen = get_current_screen();
		if ( is_null( $screen ) || 'edit-post' !== $screen->id || 'post' !== $screen->post_type ) {
			return $notices;
		}
		$dismissed_items                = new Dismissed_Items( $this->user_options );
		$escape_and_wrap_notice_content = function( $message ) {
			$message = wp_kses(
				$message,
				array(
					'a' => array(
						'href' => array(),
					),
				)
			);
			return '' . $message . '
';
		};
		$notices[] = new Notice(
			self::SLUG_SAVED_IDEAS,
			array(
				'content'         => function() use ( $escape_and_wrap_notice_content ) {
					$message_body = sprintf(
						/* translators: %s: URL to saved ideas */
						__( 'Want some inspiration for a new post? Revisit your saved ideas in Site Kit.', 'google-site-kit' ),
						esc_url( $this->context->admin_url( 'dashboard', array( 'idea-hub-tab' => 'saved-ideas' ) ) )
					);
					$message = sprintf(
						/* translators: 1: Plugin name. 2: Message. */
						__( '%1$s: %2$s', 'google-site-kit' ),
						__( 'Site Kit by Google', 'google-site-kit' ),
						$message_body
					);
					return $escape_and_wrap_notice_content( $message );
				},
				'type'            => Notice::TYPE_INFO,
				'active_callback' => function() use ( $dismissed_items ) {
					try {
						$saved_ideas = $this->get_cached_ideas( 'saved-ideas' );
						$has_saved_ideas = ! empty( $saved_ideas );
						if ( ! $has_saved_ideas && $dismissed_items->is_dismissed( self::SLUG_SAVED_IDEAS ) ) {
							// Saved items no longer need to be dismissed as there are none currently.
							$dismissed_items->add( self::SLUG_SAVED_IDEAS, -1 );
						}
						if ( $dismissed_items->is_dismissed( self::SLUG_SAVED_IDEAS ) ) {
							return false;
						}
						return $has_saved_ideas;
					} catch ( Exception $exception ) {
						return false;
					}
				},
				'dismissible'     => true,
			)
		);
		$notices[] = new Notice(
			self::SLUG_NEW_IDEAS,
			array(
				'content'         => function() use ( $escape_and_wrap_notice_content ) {
					$message_body = sprintf(
						/* translators: %s: URL to saved ideas */
						__( 'Want some inspiration for a new post? Review your new ideas in Site Kit.', 'google-site-kit' ),
						esc_url( $this->context->admin_url( 'dashboard', array( 'idea-hub-tab' => 'new-ideas' ) ) )
					);
					$message = sprintf(
						/* translators: 1: Plugin name. 2: Message. */
						__( '%1$s: %2$s', 'google-site-kit' ),
						__( 'Site Kit by Google', 'google-site-kit' ),
						$message_body
					);
					return $escape_and_wrap_notice_content( $message );
				},
				'type'            => Notice::TYPE_INFO,
				'active_callback' => function() use ( $dismissed_items ) {
					if (
						$dismissed_items->is_dismissed( self::SLUG_NEW_IDEAS )
						|| $dismissed_items->is_dismissed( self::SLUG_SAVED_IDEAS )
					) {
						return false;
					}
					try {
						$saved_ideas = $this->get_cached_ideas( 'saved-ideas' );
						if ( ! empty( $saved_ideas ) ) {
							// Don't show new ideas notice if there are saved ideas,
							// irrespective of whether we show them the saved ideas notice.
							return false;
						}
						$new_ideas = $this->get_cached_ideas( 'new-ideas' );
						return ! empty( $new_ideas );
					} catch ( Exception $exception ) {
						return false;
					}
				},
				'dismissible'     => true,
			)
		);
		return $notices;
	}
	/**
	 * Gets ideas from cache.
	 *
	 * If cache has expired, ideas will be fetched and cached if successful.
	 *
	 * @since 1.50.0
	 *
	 * @param string $datapoint The datapoint to fetch ideas from.
	 * @return array Cached or freshly cached ideas.
	 * @throws InvalidArgumentException Thrown if invalid datapoint is given.
	 * @throws RuntimeException Thrown if get_data returns a WP_Error.
	 */
	protected function get_cached_ideas( $datapoint ) {
		if ( empty( self::DATAPOINT_TRANSIENT_MAP[ $datapoint ] ) ) {
			throw new InvalidArgumentException( "Invalid datapoint $datapoint" );
		}
		$transient = self::DATAPOINT_TRANSIENT_MAP[ $datapoint ];
		$ideas     = $this->transients->get( $transient );
		if ( is_array( $ideas ) ) {
			return $ideas;
		}
		$ideas = $this->get_data( $datapoint );
		if ( is_wp_error( $ideas ) ) {
			throw new RuntimeException( $ideas->get_error_message() );
		}
		$this->transients->set( $transient, $ideas, HOUR_IN_SECONDS );
		return $ideas;
	}
	/**
	 * Gets required Google OAuth scopes for the module.
	 *
	 * @since 1.32.0
	 *
	 * @return array List of Google OAuth scopes.
	 */
	public function get_scopes() {
		return array(
			'https://www.googleapis.com/auth/ideahub.read',
			'https://www.googleapis.com/auth/ideahub.full',
		);
	}
	/**
	 * Checks whether the module is connected.
	 *
	 * A module being connected means that all steps required as part of its activation are completed.
	 *
	 * @since 1.32.0
	 *
	 * @return bool True if module is connected, false otherwise.
	 */
	public function is_connected() {
		$required_keys = array( 'tosAccepted' );
		$options = $this->get_settings()->get();
		foreach ( $required_keys as $required_key ) {
			if ( empty( $options[ $required_key ] ) ) {
				return false;
			}
		}
		return parent::is_connected();
	}
	/**
	 * Cleans up when the module is deactivated.
	 *
	 * @since 1.32.0
	 */
	public function on_deactivation() {
		$this->get_settings()->delete();
		$this->transients->delete( self::TRANSIENT_NEW_IDEAS );
		$this->transients->delete( self::TRANSIENT_SAVED_IDEAS );
	}
	/**
	 * Gets an array of debug field definitions.
	 *
	 * @since 1.32.0
	 *
	 * @return array
	 */
	public function get_debug_fields() {
		$settings = $this->get_settings()->get();
		return array();
	}
	/**
	 * Gets map of datapoint to definition data for each.
	 *
	 * @since 1.32.0
	 *
	 * @return array Map of datapoints to their definitions.
	 */
	protected function get_datapoint_definitions() {
		return array(
			'POST:create-idea-draft-post' => array( 'service' => '' ),
			'GET:draft-post-ideas'        => array(
				'service'   => '',
				'shareable' => Feature_Flags::enabled( 'dashboardSharing' ),
			),
			'GET:new-ideas'               => array(
				'service'   => 'ideahub',
				'shareable' => Feature_Flags::enabled( 'dashboardSharing' ),
			),
			'GET:published-post-ideas'    => array(
				'service'   => '',
				'shareable' => Feature_Flags::enabled( 'dashboardSharing' ),
			),
			'GET:saved-ideas'             => array(
				'service'   => 'ideahub',
				'shareable' => Feature_Flags::enabled( 'dashboardSharing' ),
			),
			'POST:update-idea-state'      => array( 'service' => 'ideahub' ),
		);
	}
	/**
	 * Creates a request object for the given datapoint.
	 *
	 * @since 1.32.0
	 *
	 * @param Data_Request $data Data request object.
	 * @return RequestInterface|callable|WP_Error Request object or callable on success, or WP_Error on failure.
	 *
	 * @throws Invalid_Datapoint_Exception Thrown if the datapoint does not exist.
	 */
	protected function create_data_request( Data_Request $data ) {
		switch ( "{$data->method}:{$data->datapoint}" ) {
			case 'POST:create-idea-draft-post':
				$expected_parameters = array(
					'name'   => 'string',
					'text'   => 'string',
					'topics' => 'array',
				);
				if ( ! isset( $data['idea'] ) ) {
					return new WP_Error(
						'missing_required_param',
						/* translators: %s: Missing parameter name */
						sprintf( __( 'Request parameter is empty: %s.', 'google-site-kit' ), 'idea' ),
						array( 'status' => 400 )
					);
				}
				$idea = $data['idea'];
				foreach ( $expected_parameters as $parameter_name => $expected_parameter_type ) {
					if ( ! isset( $idea[ $parameter_name ] ) ) {
						return new WP_Error(
							'missing_required_param',
							/* translators: %s: Missing parameter name */
							sprintf( __( 'Request idea parameter is empty: %s.', 'google-site-kit' ), $parameter_name ),
							array( 'status' => 400 )
						);
					}
					$parameter_type = gettype( $idea[ $parameter_name ] );
					if ( $parameter_type !== $expected_parameter_type ) {
						return new WP_Error(
							'wrong_parameter_type',
							sprintf(
								/* translators: 1: parameter name, 2: expected type, 3: received type */
								__( 'Wrong parameter type for %1$s, expected %2$s, received %3$s', 'google-site-kit' ),
								$parameter_name,
								$expected_parameter_type,
								$parameter_type
							),
							array( 'status' => 400 )
						);
					}
				}
				return function() use ( $idea ) {
					// Allows us to create a blank post.
					add_filter( 'wp_insert_post_empty_content', '__return_false' );
					$post_id = wp_insert_post( array(), false );
					remove_filter( 'wp_insert_post_empty_content', '__return_false' );
					if ( 0 === $post_id ) {
						return new WP_Error(
							'unable_to_draft_post',
							__( 'Unable to draft post.', 'google-site-kit' ),
							array( 'status' => 400 )
						);
					}
					$this->set_post_idea( $post_id, $idea );
					$this->track_idea_activity( $post_id, self::ACTIVITY_POST_DRAFTED );
					$this->transients->delete( self::TRANSIENT_SAVED_IDEAS );
					$this->transients->delete( self::TRANSIENT_NEW_IDEAS );
					return $post_id;
				};
			case 'GET:draft-post-ideas':
				return function() {
					return $this->query_idea_posts( 'draft' );
				};
			case 'GET:new-ideas':
				return $this->fetch_ideas( 'new' );
			case 'GET:published-post-ideas':
				return function() {
					$statuses = array( 'publish', 'future', 'private' );
					return $this->query_idea_posts( $statuses );
				};
			case 'GET:saved-ideas':
				return $this->fetch_ideas( 'saved' );
			case 'POST:update-idea-state':
				if ( ! isset( $data['name'] ) ) {
					return new WP_Error(
						'missing_required_param',
						/* translators: %s: Missing parameter name */
						sprintf( __( 'Request parameter is empty: %s.', 'google-site-kit' ), 'name' ),
						array( 'status' => 400 )
					);
				}
				if ( ! isset( $data['saved'] ) && ! isset( $data['dismissed'] ) ) {
					return new WP_Error(
						'missing_required_param',
						__( 'Either "saved" or "dismissed" parameter must be provided.', 'google-site-kit' ),
						array( 'status' => 400 )
					);
				}
				$idea_name       = $data['name'];
				$idea_name_parts = explode( '/', $data['name'] );
				$parent = $this->get_parent_slug();
				$parent = sprintf(
					'%s/ideaStates/%s',
					untrailingslashit( $parent ),
					array_pop( $idea_name_parts )
				);
				$update_mask = array();
				$body = new Google_Service_Ideahub_GoogleSearchIdeahubV1betaIdeaState();
				$body->setName( $idea_name );
				if ( isset( $data['saved'] ) ) {
					$body->setSaved( filter_var( $data['saved'], FILTER_VALIDATE_BOOLEAN ) );
					$update_mask[] = 'saved';
				}
				if ( isset( $data['dismissed'] ) ) {
					$body->setDismissed( filter_var( $data['dismissed'], FILTER_VALIDATE_BOOLEAN ) );
					$update_mask[] = 'dismissed';
				}
				$params = array(
					'updateMask' => implode( ',', $update_mask ),
				);
				return $this->get_service( 'ideahub' )->platforms_properties_ideaStates->patch( $parent, $body, $params );
		}
		return parent::create_data_request( $data );
	}
	/**
	 * Parses a response for the given datapoint.
	 *
	 * @since 1.34.0
	 *
	 * @param Data_Request $data     Data request object.
	 * @param mixed        $response Request response.
	 *
	 * @return mixed Parsed response data on success, or WP_Error on failure.
	 */
	protected function parse_data_response( Data_Request $data, $response ) {
		$filter_draft_post_response = function( $post_id ) {
			return array_merge(
				array(
					'postID'      => $post_id,
					'postEditURL' => get_edit_post_link( $post_id, null ),
				),
				$this->get_post_idea( $post_id )
			);
		};
		switch ( "{$data->method}:{$data->datapoint}" ) {
			case 'POST:create-idea-draft-post':
				$this->interaction_count->increment();
				return $filter_draft_post_response( $response );
			case 'GET:draft-post-ideas':
				return array_filter(
					array_map(
						$filter_draft_post_response,
						is_array( $response ) ? $response : array( $response )
					)
				);
			case 'GET:new-ideas':
				$ideas = $this->filter_out_ideas_with_posts( $response->getIdeas() );
				return array_map( array( self::class, 'filter_idea_with_id' ), $ideas );
			case 'GET:published-post-ideas':
				return array_filter(
					array_map(
						function( $post_id ) {
							return array_merge(
								array(
									'postID'      => $post_id,
									'postEditURL' => get_edit_post_link( $post_id ),
									'postURL'     => get_permalink( $post_id ),
								),
								$this->get_post_idea( $post_id )
							);
						},
						is_array( $response ) ? $response : array( $response )
					)
				);
			case 'GET:saved-ideas':
				$ideas = $this->filter_out_ideas_with_posts( $response->getIdeas() );
				return array_map( array( self::class, 'filter_idea_with_id' ), $ideas );
			case 'POST:update-idea-state':
				$this->interaction_count->increment();
				$this->transients->delete( self::TRANSIENT_SAVED_IDEAS );
				$this->transients->delete( self::TRANSIENT_NEW_IDEAS );
				return self::filter_idea_state_with_id( $response );
		}
		return parent::parse_data_response( $data, $response );
	}
	/**
	 * Sets up information about the module.
	 *
	 * @since 1.32.0
	 *
	 * @return array Associative array of module info.
	 */
	protected function setup_info() {
		return array(
			'slug'        => self::MODULE_SLUG,
			'name'        => _x( 'Idea Hub', 'Service name', 'google-site-kit' ),
			'description' => __( 'Idea Hub suggests what you can write about next, from actual questions people asked on Google Search', 'google-site-kit' ),
			'order'       => 7,
		);
	}
	/**
	 * Sets up the module's settings instance.
	 *
	 * @since 1.32.0
	 *
	 * @return Module_Settings
	 */
	protected function setup_settings() {
		return new Settings( $this->options );
	}
	/**
	 * Sets up the module's assets to register.
	 *
	 * @since 1.32.0
	 *
	 * @return Asset[] List of Asset objects.
	 */
	protected function setup_assets() {
		$base_url = $this->context->url( 'dist/assets/' );
		return array(
			new Script(
				'googlesitekit-modules-idea-hub',
				array(
					'src'          => $base_url . 'js/googlesitekit-modules-idea-hub.js',
					'dependencies' => array(
						'googlesitekit-vendor',
						'googlesitekit-api',
						'googlesitekit-data',
						'googlesitekit-modules',
						'googlesitekit-components',
					),
				)
			),
			new Script(
				'googlesitekit-idea-hub-post-list',
				array(
					'src'           => $base_url . 'js/googlesitekit-idea-hub-post-list.js',
					'load_contexts' => array( Asset::CONTEXT_ADMIN_POSTS ),
					'dependencies'  => array(
						'googlesitekit-i18n',
						'googlesitekit-datastore-location',
						'googlesitekit-datastore-ui',
						'googlesitekit-datastore-user',
						'googlesitekit-modules',
						'googlesitekit-components',
					),
				)
			),
			new Script(
				'googlesitekit-idea-hub-notice',
				array(
					'src'           => $base_url . 'js/googlesitekit-idea-hub-notice.js',
					'dependencies'  => array(
						'googlesitekit-i18n',
						'wp-data',
						'wp-api-fetch',
						'wp-polyfill',
						'wp-url',
						'googlesitekit-components',
					),
					'load_contexts' => array( Asset::CONTEXT_ADMIN_POST_EDITOR ),
				)
			),
			new Script_Data(
				'googlesitekit-idea-hub-data',
				array(
					'global'        => '_googlesitekitIdeaHub',
					'data_callback' => function () {
						return array(
							'lastIdeaPostUpdatedAt' => $this->transients->get( self::IDEA_HUB_LAST_CHANGED ),
							'interactionCount'      => $this->interaction_count->get(),
						);
					},
				)
			),
		);
	}
	/**
	 * Sets up the Google services the module should use.
	 *
	 * This method is invoked once by {@see Module::get_service()} to lazily set up the services when one is requested
	 * for the first time.
	 *
	 * @since 1.40.0
	 *
	 * @param Google_Site_Kit_Client $client Google client instance.
	 * @return array Google services as $identifier => $service_instance pairs.
	 */
	protected function setup_services( Google_Site_Kit_Client $client ) {
		return array(
			'ideahub' => new Google_Service_Ideahub( $client ),
		);
	}
	/**
	 * Saves post idea settings.
	 *
	 * @since 1.33.0
	 *
	 * @param int   $post_id Post ID.
	 * @param array $idea    Idea settings.
	 */
	public function set_post_idea( $post_id, array $idea ) {
		$idea = wp_parse_args(
			$idea,
			array(
				'name'   => '',
				'text'   => '',
				'topics' => array(),
			)
		);
		$this->post_name_setting->set( $post_id, $idea['name'] );
		$this->post_text_setting->set( $post_id, $idea['text'] );
		$this->post_topic_setting->set( $post_id, $idea['topics'] );
	}
	/**
	 * Parses an idea ID, adds it to the model object and returns updated model.
	 *
	 * @since 1.40.0
	 *
	 * @param Google_Model $idea Idea model.
	 * @return \stdClass Updated model with _id attribute.
	 */
	public static function filter_idea_with_id( $idea ) {
		$obj = $idea->toSimpleObject();
		$matches = array();
		if ( preg_match( '#ideas/([^/]+)#', $idea['name'], $matches ) ) {
			$obj->_id = $matches[1];
		}
		return $obj;
	}
	/**
	 * Parses an idea state ID, adds it to the model object and returns updated model.
	 *
	 * @since 1.40.0
	 *
	 * @param Google_Model $idea_state Idea state model.
	 * @return \stdClass Updated model with _id attribute.
	 */
	public static function filter_idea_state_with_id( $idea_state ) {
		$obj = $idea_state->toSimpleObject();
		$matches = array();
		if ( preg_match( '#platforms/([^/]+)/properties/([^/]+)/ideaStates/([^/]+)#', $idea_state['name'], $matches ) ) {
			$obj->_id = $matches[3];
		}
		return $obj;
	}
	/**
	 * Gets post idea settings.
	 *
	 * @since 1.33.0
	 *
	 * @param int $post_id Post ID.
	 * @return array|null Post idea settings array. Returns NULL if a post doesn't have an associated idea.
	 */
	public function get_post_idea( $post_id ) {
		$name   = $this->post_name_setting->get( $post_id );
		$text   = $this->post_text_setting->get( $post_id );
		$topics = $this->post_topic_setting->get( $post_id );
		if ( empty( $name ) || empty( $text ) || empty( $topics ) ) {
			return null;
		}
		return array(
			'name'   => $name,
			'text'   => $text,
			'topics' => $topics,
		);
	}
	/**
	 * Checks whether the post is an Idea Hub post.
	 *
	 * @since 1.36.0
	 *
	 * @param int $post_id Post ID.
	 * @return bool True if the post with supplied ID is an Idea Hub post.
	 */
	private function is_idea_post( $post_id ) {
		return is_array( $this->get_post_idea( $post_id ) );
	}
	/**
	 * Hook to check whether an Idea Hub post status has changed.
	 *
	 * @since 1.40.0
	 *
	 * @param string  $new_status Updated post status.
	 * @param string  $old_status Previous post status.
	 * @param WP_Post $post The post in question.
	 */
	private function on_idea_hub_post_status_transition( $new_status, $old_status, $post ) {
		if ( $new_status === $old_status || ! $this->is_idea_post( $post->ID ) ) {
			return;
		}
		$this->transients->set( self::IDEA_HUB_LAST_CHANGED, time() );
		if ( 'publish' === $new_status ) {
			$this->track_idea_activity( $post->ID, self::ACTIVITY_POST_PUBLISHED );
		} elseif ( 'publish' === $old_status ) {
			$this->track_idea_activity( $post->ID, self::ACTIVITY_POST_UNPUBLISHED );
		}
	}
	/**
	 * Adds .googlesitekit-idea-hub__draft class to idea posts on the posts page.
	 *
	 * @since 1.40.0
	 *
	 * @param array $classes An array of post class names.
	 * @param array $class An array of additional class names added to the post.
	 * @param int   $post_id The post ID.
	 * @return array An array of post class names.
	 */
	private function update_post_classes( $classes, $class, $post_id ) {
		// Do nothing on the frontend.
		if ( ! is_admin() ) {
			return $classes;
		}
		$screen = get_current_screen();
		if ( is_null( $screen ) || 'edit-post' !== $screen->id || 'post' !== $screen->post_type ) {
			return $classes;
		}
		if ( $this->is_idea_post( $post_id ) ) {
			$classes[] = 'googlesitekit-idea-hub__post';
			if ( ! wp_style_is( 'googlesitekit-admin-css' ) ) {
				$this->assets->enqueue_asset( 'googlesitekit-admin-css' );
			}
		}
		return $classes;
	}
	/**
	 * Gets the parent slug to use for Idea Hub API requests.
	 *
	 * @since 1.40.0
	 *
	 * @return string Parent slug.
	 */
	private function get_parent_slug() {
		$reference_url = $this->context->get_reference_site_url();
		$reference_url = rawurlencode( $reference_url );
		return "platforms/sitekit/properties/{$reference_url}";
	}
	/**
	 * Pulls posts created for an idea from the database.
	 *
	 * @since 1.40.0
	 *
	 * @param string|array $post_status Post status or statuses.
	 * @return array An array of post IDs.
	 */
	private function query_idea_posts( $post_status ) {
		$wp_query = new \WP_Query();
		return $wp_query->query(
			array(
				'fields'                 => 'ids',
				'post_status'            => $post_status,
				'posts_per_page'         => 500, // phpcs:ignore WordPress.WP.PostsPerPage.posts_per_page_posts_per_page
				'no_found_rows'          => true,
				'update_post_term_cache' => false,
				'order'                  => 'DESC',
				'orderby'                => 'ID',
				'meta_query'             => array( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
					array(
						'key'     => Post_Idea_Name::META_KEY,
						'compare' => 'EXISTS',
					),
				),
			)
		);
	}
	/**
	 * Fetches ideas from the Idea Hub API.
	 *
	 * @since 1.40.0
	 *
	 * @param string $type Ideas type. Valid values "saved", "new" or an empty string which means all ideas.
	 * @return mixed List ideas request.
	 */
	private function fetch_ideas( $type ) {
		$parent = $this->get_parent_slug();
		$params = array(
			'pageSize' => 100,
		);
		if ( 'saved' === $type ) {
			$params['filter'] = 'saved(true)';
		} elseif ( 'new' === $type ) {
			$params['filter'] = 'saved(false)';
		}
		return $this->get_service( 'ideahub' )
			->platforms_properties_ideas
			->listPlatformsPropertiesIdeas( $parent, $params );
	}
	/**
	 * Filters out ideas for which we have already created a post.
	 *
	 * @since 1.40.0
	 *
	 * @param array $ideas Ideas list to filter.
	 * @return array Filtered ideas list.
	 */
	private function filter_out_ideas_with_posts( $ideas ) {
		if ( empty( $ideas ) ) {
			return $ideas;
		}
		$names = wp_list_pluck( $ideas, 'name' );
		$statuses = array( 'publish', 'pending', 'draft', 'future', 'private' );
		$posts    = $this->query_idea_posts( $statuses );
		if ( empty( $posts ) ) {
			return $ideas;
		}
		$ideas_with_posts = array();
		foreach ( $posts as $post_id ) {
			$idea = $this->get_post_idea( $post_id );
			if ( ! empty( $idea['name'] ) ) {
				$ideas_with_posts[] = $idea['name'];
			}
		}
		$ideas = array_filter(
			$ideas,
			function( $idea ) use ( $ideas_with_posts ) {
				return ! in_array( $idea->getName(), $ideas_with_posts, true );
			}
		);
		return array_values( $ideas );
	}
	/**
	 * Tracks an idea activity.
	 *
	 * @since 1.42.0
	 *
	 * @param int    $post_id Post ID.
	 * @param string $type    Activity type.
	 */
	private function track_idea_activity( $post_id, $type ) {
		$post = get_post( $post_id );
		$name = $this->post_name_setting->get( $post->ID );
		if ( empty( $name ) ) {
			return;
		}
		$parent   = $this->get_parent_slug();
		$activity = new Google_Service_Ideahub_GoogleSearchIdeahubV1betaIdeaActivity();
		$activity->setIdeas( array( $name ) );
		$activity->setTopics( array() );
		$activity->setType( $type );
		if ( 'publish' === $post->post_status ) {
			$uri = get_permalink( $post );
			if ( ! empty( $uri ) ) {
				$activity->setUri( $uri );
			}
		}
		try {
			$this->get_service( 'ideahub' )
				->platforms_properties_ideaActivities
				->create( $parent, $activity );
		} catch ( Exception $e ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch
			// Do nothing.
		}
	}
}
                                                                                                                                                                                    Optimize.php                                                                                        0000644                 00000016464 15051555666 0007106 0                                                                                                    ustar 00                                                                                                                                                                                                                                                       gtag_config_add_optimize_id( $gtag_config );
			}
		);
		$print_amp_optimize_experiment = function() {
			$this->print_amp_optimize_experiment();
		};
		add_action( 'wp_footer', $print_amp_optimize_experiment ); // For AMP Native and Transitional.
		add_action( 'amp_post_template_footer', $print_amp_optimize_experiment ); // For AMP Reader.
		add_filter( // Load amp-experiment component for AMP Reader.
			'amp_post_template_data',
			function( $data ) {
				return $this->amp_data_load_experiment_component( $data );
			}
		);
		// Optimize tag placement logic.
		add_action( 'template_redirect', $this->get_method_proxy( 'register_tag' ) );
	}
	/**
	 * Checks whether the module is connected.
	 *
	 * A module being connected means that all steps required as part of its activation are completed.
	 *
	 * @since 1.0.0
	 *
	 * @return bool True if module is connected, false otherwise.
	 */
	public function is_connected() {
		$settings = $this->get_settings()->get();
		if ( ! $settings['optimizeID'] ) {
			return false;
		}
		return parent::is_connected();
	}
	/**
	 * Cleans up when the module is deactivated.
	 *
	 * @since 1.0.0
	 */
	public function on_deactivation() {
		$this->get_settings()->delete();
	}
	/**
	 * Expands gtag config options with Optimize Container ID.
	 *
	 * @since 1.0.0
	 *
	 * @param array $gtag_config Associative array of gtag config options.
	 * @return array Filtered $gtag_config.
	 */
	protected function gtag_config_add_optimize_id( $gtag_config ) {
		$settings = $this->get_settings()->get();
		if ( $settings['optimizeID'] ) {
			$gtag_config['optimize_id'] = $settings['optimizeID'];
		}
		return $gtag_config;
	}
	/**
	 * Outputs Optimize experiment script in AMP if opted in.
	 *
	 * @since 1.0.0
	 */
	protected function print_amp_optimize_experiment() {
		if ( ! $this->context->is_amp() ) {
			return;
		}
		$settings = $this->get_settings()->get();
		if ( ! $settings['ampExperimentJSON'] ) {
			return;
		}
		?>
		
			
		
		get_settings()->get();
		return array(
			'optimize_id' => array(
				'label' => __( 'Optimize Container ID', 'google-site-kit' ),
				'value' => $settings['optimizeID'],
				'debug' => Debug_Data::redact_debug_value( $settings['optimizeID'], 7 ),
			),
		);
	}
	/**
	 * Adds AMP experiment script if opted in.
	 *
	 * @since 1.0.0
	 *
	 * @param array $data AMP template data.
	 * @return array Filtered $data.
	 */
	protected function amp_data_load_experiment_component( $data ) {
		$settings = $this->get_settings()->get();
		if ( $settings['ampExperimentJSON'] ) {
			$data['amp_component_scripts']['amp-experiment'] = 'https://cdn.ampproject.org/v0/amp-experiment-0.1.js';
		}
		return $data;
	}
	/**
	 * Sets up information about the module.
	 *
	 * @since 1.0.0
	 *
	 * @return array Associative array of module info.
	 */
	protected function setup_info() {
		return array(
			'slug'        => 'optimize',
			'name'        => _x( 'Optimize', 'Service name', 'google-site-kit' ),
			'description' => __( 'Create free A/B tests that help you drive metric-based design solutions to your site', 'google-site-kit' ),
			'order'       => 5,
			'homepage'    => __( 'https://optimize.google.com/optimize/home/', 'google-site-kit' ),
			'depends_on'  => array( 'analytics' ),
		);
	}
	/**
	 * Sets up the Google services the module should use.
	 *
	 * This method is invoked once by {@see Module::get_service()} to lazily set up the services when one is requested
	 * for the first time.
	 *
	 * @since 1.0.0
	 * @since 1.2.0 Now requires Google_Site_Kit_Client instance.
	 *
	 * @param Google_Site_Kit_Client $client Google client instance.
	 * @return array Google services as $identifier => $service_instance pairs. Every $service_instance must be an
	 *               instance of Google_Service.
	 */
	protected function setup_services( Google_Site_Kit_Client $client ) {
		return array();
	}
	/**
	 * Sets up the module's settings instance.
	 *
	 * @since 1.2.0
	 *
	 * @return Module_Settings
	 */
	protected function setup_settings() {
		return new Settings( $this->options );
	}
	/**
	 * Sets up the module's assets to register.
	 *
	 * @since 1.10.0
	 *
	 * @return Asset[] List of Asset objects.
	 */
	protected function setup_assets() {
		$base_url = $this->context->url( 'dist/assets/' );
		return array(
			new Script(
				'googlesitekit-modules-optimize',
				array(
					'src'          => $base_url . 'js/googlesitekit-modules-optimize.js',
					'dependencies' => array(
						'googlesitekit-vendor',
						'googlesitekit-api',
						'googlesitekit-data',
						'googlesitekit-modules',
						'googlesitekit-datastore-site',
						'googlesitekit-datastore-forms',
						'googlesitekit-components',
					),
				)
			),
		);
	}
	/**
	 * Registers the Optimize tag.
	 *
	 * @since 1.39.0
	 */
	private function register_tag() {
		$is_amp          = $this->context->is_amp();
		$module_settings = $this->get_settings();
		$settings        = $module_settings->get();
		if ( $is_amp ) {
			return false;
		}
		$tag = new Web_Tag( $settings['optimizeID'], self::MODULE_SLUG );
		if ( ! $tag->is_tag_blocked() ) {
			$tag->use_guard( new Tag_Guard( $module_settings ) );
			if ( $tag->can_register() ) {
				$tag->register();
			}
		}
	}
}
                                                                                                                                                                                                            Analytics_4/Web_Tag.php                                                                             0000644                 00000002756 15051555666 0010767 0                                                                                                    ustar 00                                                                                                                                                                                                                                                       get_method_proxy( 'enqueue_gtag_script' ), 20 );
		$this->do_init_tag_action();
	}
	/**
	 * Enqueues gtag script.
	 *
	 * @since 1.31.0
	 */
	protected function enqueue_gtag_script() {
		if ( did_action( 'googlesitekit_analytics_init_tag' ) ) {
			// If the gtag script is already registered in the Analytics module, then we need to add  configuration only.
			$config = sprintf( 'gtag("config", "%s");', esc_js( $this->tag_id ) );
			wp_add_inline_script( 'google_gtagjs', $config );
		} else {
			// Otherwise register gtag as in the Analytics module knowing that we used Measurement ID from GA4 instead of Property ID.
			parent::enqueue_gtag_script();
		}
	}
}
                  Analytics_4/Settings.php                                                                            0000644                 00000005273 15051555666 0011254 0                                                                                                    ustar 00                                                                                                                                                                                                                                                       register_owned_keys();
	}
	/**
	 * Returns keys for owned settings.
	 *
	 * @since 1.30.0
	 *
	 * @return array An array of keys for owned settings.
	 */
	public function get_owned_keys() {
		return array(
			// TODO: These can be uncommented when Analytics and Analytics 4 modules are officially separated.
			/* 'accountID', */ // phpcs:ignore Squiz.PHP.CommentedOutCode.Found
			/* 'adsConversionID', */ // phpcs:ignore Squiz.PHP.CommentedOutCode.Found
			'propertyID',
			'webDataStreamID',
			'measurementID',
			'googleTagID',
			'googleTagAccountID',
			'googleTagContainerID',
		);
	}
	/**
	 * Gets the default value.
	 *
	 * @since 1.30.0
	 *
	 * @return array
	 */
	protected function get_default() {
		return array(
			'ownerID'              => 0,
			// TODO: These can be uncommented when Analytics and Analytics 4 modules are officially separated.
			/* 'accountID'       		=> '', */ // phpcs:ignore Squiz.PHP.CommentedOutCode.Found
			/* 'adsConversionID' 		=> '', */ // phpcs:ignore Squiz.PHP.CommentedOutCode.Found
			'propertyID'           => '',
			'webDataStreamID'      => '',
			'measurementID'        => '',
			'useSnippet'           => true,
			'googleTagID'          => '',
			'googleTagAccountID'   => '',
			'googleTagContainerID' => '',
		);
	}
	/**
	 * Gets the callback for sanitizing the setting's value before saving.
	 *
	 * @since 1.30.0
	 *
	 * @return callable|null
	 */
	protected function get_sanitize_callback() {
		return function( $option ) {
			if ( is_array( $option ) ) {
				if ( isset( $option['useSnippet'] ) ) {
					$option['useSnippet'] = (bool) $option['useSnippet'];
				}
				if ( isset( $option['googleTagID'] ) ) {
					if ( ! preg_match( '/^(G|GT|AW)-[a-zA-Z0-9]+$/', $option['googleTagID'] ) ) {
						$option['googleTagID'] = '';
					}
				}
			}
			return $option;
		};
	}
}
                                                                                                                                                                                                                                                                                                                                     Analytics_4/Tag_Guard.php                                                                           0000644                 00000001557 15051555666 0011312 0                                                                                                    ustar 00                                                                                                                                                                                                                                                       settings->get();
		return ! empty( $settings['useSnippet'] ) && ! empty( $settings['measurementID'] );
	}
}
                                                                                                                                                 Analytics.php                                                                                       0000644                 00000144516 15051555666 0007235 0                                                                                                    ustar 00                                                                                                                                                                                                                                                       register_scopes_hook();
		/**
		 * This filter only exists to be unhooked by the AdSense module if active.
		 *
		 * @see \Google\Site_Kit\Modules\Analytics\Settings::register
		 */
		add_filter( 'googlesitekit_analytics_adsense_linked', '__return_false' );
		add_action( 'admin_init', $this->get_method_proxy( 'handle_provisioning_callback' ) );
		add_action( 'googlesitekit_authorize_user', array( $this, 'handle_token_response_data' ) );
		// For non-AMP and AMP.
		add_action( 'wp_head', $this->get_method_proxy( 'print_tracking_opt_out' ), 0 );
		// For Web Stories plugin.
		add_action( 'web_stories_story_head', $this->get_method_proxy( 'print_tracking_opt_out' ), 0 );
		// Analytics tag placement logic.
		add_action( 'template_redirect', $this->get_method_proxy( 'register_tag' ) );
		add_filter(
			'googlesitekit_proxy_setup_mode',
			function( $original_mode ) {
				return ! $this->is_connected()
					? 'analytics-step'
					: $original_mode;
			}
		);
		( new Advanced_Tracking( $this->context ) )->register();
	}
	/**
	 * Checks whether or not tracking snippet should be contextually disabled for this request.
	 *
	 * @since 1.1.0
	 *
	 * @return bool
	 */
	protected function is_tracking_disabled() {
		$settings = $this->get_settings()->get();
		// This filter is documented in Tag_Manager::filter_analytics_allow_tracking_disabled.
		if ( ! apply_filters( 'googlesitekit_allow_tracking_disabled', $settings['useSnippet'] ) ) {
			return false;
		}
		$option = $this->get_settings()->get();
		$disable_logged_in_users  = in_array( 'loggedinUsers', $option['trackingDisabled'], true ) && is_user_logged_in();
		$disable_content_creators = in_array( 'contentCreators', $option['trackingDisabled'], true ) && current_user_can( 'edit_posts' );
		$disabled = $disable_logged_in_users || $disable_content_creators;
		/**
		 * Filters whether or not the Analytics tracking snippet is output for the current request.
		 *
		 * @since 1.1.0
		 *
		 * @param $disabled bool Whether to disable tracking or not.
		 */
		return (bool) apply_filters( 'googlesitekit_analytics_tracking_disabled', $disabled );
	}
	/**
	 * Gets required Google OAuth scopes for the module.
	 *
	 * @since 1.0.0
	 *
	 * @return array List of Google OAuth scopes.
	 */
	public function get_scopes() {
		return array(
			self::READONLY_SCOPE,
		);
	}
	/**
	 * Checks whether the module is connected.
	 *
	 * A module being connected means that all steps required as part of its activation are completed.
	 *
	 * @since 1.0.0
	 *
	 * @return bool True if module is connected, false otherwise.
	 */
	public function is_connected() {
		$required_keys = array(
			'accountID',
			'propertyID',
			'profileID',
			'internalWebPropertyID',
		);
		$options = $this->get_settings()->get();
		foreach ( $required_keys as $required_key ) {
			if ( empty( $options[ $required_key ] ) ) {
				return false;
			}
		}
		return parent::is_connected();
	}
	/**
	 * Cleans up when the module is deactivated.
	 *
	 * @since 1.0.0
	 */
	public function on_deactivation() {
		$this->get_settings()->delete();
		$this->options->delete( 'googlesitekit_analytics_adsense_linked' );
	}
	/**
	 * Gets an array of debug field definitions.
	 *
	 * @since 1.5.0
	 *
	 * @return array
	 */
	public function get_debug_fields() {
		$settings = $this->get_settings()->get();
		return array(
			'analytics_account_id'  => array(
				'label' => __( 'Analytics account ID', 'google-site-kit' ),
				'value' => $settings['accountID'],
				'debug' => Debug_Data::redact_debug_value( $settings['accountID'] ),
			),
			'analytics_property_id' => array(
				'label' => __( 'Analytics property ID', 'google-site-kit' ),
				'value' => $settings['propertyID'],
				'debug' => Debug_Data::redact_debug_value( $settings['propertyID'], 7 ),
			),
			'analytics_profile_id'  => array(
				'label' => __( 'Analytics view ID', 'google-site-kit' ),
				'value' => $settings['profileID'],
				'debug' => Debug_Data::redact_debug_value( $settings['profileID'] ),
			),
			'analytics_use_snippet' => array(
				'label' => __( 'Analytics snippet placed', 'google-site-kit' ),
				'value' => $settings['useSnippet'] ? __( 'Yes', 'google-site-kit' ) : __( 'No', 'google-site-kit' ),
				'debug' => $settings['useSnippet'] ? 'yes' : 'no',
			),
		);
	}
	/**
	 * Handles the provisioning callback after the user completes the terms of service.
	 *
	 * @since 1.9.0
	 */
	protected function handle_provisioning_callback() {
		if ( defined( 'WP_CLI' ) && WP_CLI ) {
			return;
		}
		if ( ! current_user_can( Permissions::MANAGE_OPTIONS ) ) {
			return;
		}
		$input = $this->context->input();
		if ( ! $input->filter( INPUT_GET, 'gatoscallback' ) ) {
			return;
		}
		// The handler should check the received Account Ticket id parameter against the id stored in the provisioning step.
		$account_ticket_id        = $input->filter( INPUT_GET, 'accountTicketId', FILTER_SANITIZE_STRING );
		$stored_account_ticket_id = get_transient( self::PROVISION_ACCOUNT_TICKET_ID . '::' . get_current_user_id() );
		delete_transient( self::PROVISION_ACCOUNT_TICKET_ID . '::' . get_current_user_id() );
		if ( $stored_account_ticket_id !== $account_ticket_id ) {
			wp_safe_redirect(
				$this->context->admin_url( 'module-analytics', array( 'error_code' => 'account_ticket_id_mismatch' ) )
			);
			exit;
		}
		// Check for a returned error.
		$error = $input->filter( INPUT_GET, 'error', FILTER_SANITIZE_STRING );
		if ( ! empty( $error ) ) {
			wp_safe_redirect(
				$this->context->admin_url( 'module-analytics', array( 'error_code' => $error ) )
			);
			exit;
		}
		$account_id      = $input->filter( INPUT_GET, 'accountId', FILTER_SANITIZE_STRING );
		$web_property_id = $input->filter( INPUT_GET, 'webPropertyId', FILTER_SANITIZE_STRING );
		$profile_id      = $input->filter( INPUT_GET, 'profileId', FILTER_SANITIZE_STRING );
		if ( empty( $account_id ) || empty( $web_property_id ) || empty( $profile_id ) ) {
			wp_safe_redirect(
				$this->context->admin_url( 'module-analytics', array( 'error_code' => 'callback_missing_parameter' ) )
			);
			exit;
		}
		// Retrieve the internal web property id.
		try {
			$web_property = $this->get_service( 'analytics' )->management_webproperties->get( $account_id, $web_property_id );
		} catch ( Exception $e ) {
			wp_safe_redirect(
				$this->context->admin_url( 'module-analytics', array( 'error_code' => 'property_not_found' ) )
			);
			exit;
		}
		$internal_web_property_id = $web_property->getInternalWebPropertyId();
		$this->get_settings()->merge(
			array(
				'accountID'             => $account_id,
				'propertyID'            => $web_property_id,
				'profileID'             => $profile_id,
				'internalWebPropertyID' => $internal_web_property_id,
			)
		);
		do_action(
			'googlesitekit_analytics_handle_provisioning_callback',
			$account_id,
			$web_property_id,
			$internal_web_property_id,
			$profile_id
		);
		wp_safe_redirect(
			$this->context->admin_url(
				'dashboard',
				array(
					'notification' => 'authentication_success',
					'slug'         => 'analytics',
				)
			)
		);
		exit;
	}
	/**
	 * Gets map of datapoint to definition data for each.
	 *
	 * @since 1.9.0
	 *
	 * @return array Map of datapoints to their definitions.
	 */
	protected function get_datapoint_definitions() {
		return array(
			'GET:accounts-properties-profiles' => array( 'service' => 'analytics' ),
			'POST:create-account-ticket'       => array(
				'service'                => 'analyticsprovisioning',
				'scopes'                 => array( self::PROVISION_SCOPE ),
				'request_scopes_message' => __( 'You’ll need to grant Site Kit permission to create a new Analytics account on your behalf.', 'google-site-kit' ),
			),
			'POST:create-profile'              => array(
				'service'                => 'analytics',
				'scopes'                 => array( self::EDIT_SCOPE ),
				'request_scopes_message' => __( 'You’ll need to grant Site Kit permission to create a new Analytics view on your behalf.', 'google-site-kit' ),
			),
			'POST:create-property'             => array(
				'service'                => 'analytics',
				'scopes'                 => array( self::EDIT_SCOPE ),
				'request_scopes_message' => __( 'You’ll need to grant Site Kit permission to create a new Analytics property on your behalf.', 'google-site-kit' ),
			),
			'GET:goals'                        => array(
				'service'   => 'analytics',
				'shareable' => Feature_Flags::enabled( 'dashboardSharing' ),
			),
			'GET:profiles'                     => array( 'service' => 'analytics' ),
			'GET:properties-profiles'          => array( 'service' => 'analytics' ),
			'GET:report'                       => array(
				'service'   => 'analyticsreporting',
				'shareable' => Feature_Flags::enabled( 'dashboardSharing' ),
			),
		);
	}
	/**
	 * Creates a request object for the given datapoint.
	 *
	 * @since 1.0.0
	 *
	 * @param Data_Request $data Data request object.
	 * @return RequestInterface|callable|WP_Error Request object or callable on success, or WP_Error on failure.
	 *
	 * @throws Invalid_Datapoint_Exception Thrown if the datapoint does not exist.
	 */
	protected function create_data_request( Data_Request $data ) {
		switch ( "{$data->method}:{$data->datapoint}" ) {
			case 'GET:accounts-properties-profiles':
				return function () use ( $data ) {
					$restore_defer = $this->with_client_defer( false );
					try {
						return $this->get_service( 'analytics' )->management_accounts->listManagementAccounts();
					} catch ( Google_Service_Exception $exception ) {
						// The exception message is a JSON object of all errors, so we'll convert it to our WP Error first.
						$wp_error = $this->exception_to_error( $exception, $data->datapoint );
						// Unfortunately there isn't a better way to identify this without checking the message.
						if ( 'User does not have any Google Analytics account.' === $wp_error->get_error_message() ) {
							return new Google_Service_Analytics_Accounts();
						}
						// If any other exception was caught, re-throw it.
						throw $exception;
					} finally {
						$restore_defer(); // Will be called before returning in all cases.
					}
				};
			case 'POST:create-account-ticket':
				if ( ! isset( $data['accountName'] ) ) {
					/* translators: %s: Missing parameter name */
					return new WP_Error( 'missing_required_param', sprintf( __( 'Request parameter is empty: %s.', 'google-site-kit' ), 'accountName' ), array( 'status' => 400 ) );
				}
				if ( ! isset( $data['propertyName'] ) ) {
					/* translators: %s: Missing parameter name */
					return new WP_Error( 'missing_required_param', sprintf( __( 'Request parameter is empty: %s.', 'google-site-kit' ), 'propertyName' ), array( 'status' => 400 ) );
				}
				if ( ! isset( $data['profileName'] ) ) {
					/* translators: %s: Missing parameter name */
					return new WP_Error( 'missing_required_param', sprintf( __( 'Request parameter is empty: %s.', 'google-site-kit' ), 'profileName' ), array( 'status' => 400 ) );
				}
				if ( ! isset( $data['timezone'] ) ) {
					/* translators: %s: Missing parameter name */
					return new WP_Error( 'missing_required_param', sprintf( __( 'Request parameter is empty: %s.', 'google-site-kit' ), 'timezone' ), array( 'status' => 400 ) );
				}
				if ( ! $this->authentication->credentials()->using_proxy() ) {
					return new WP_Error( 'requires_service', __( 'Analytics provisioning requires connecting via the Site Kit Service.', 'google-site-kit' ), array( 'status' => 400 ) );
				}
				$account = new Google_Service_Analytics_Account();
				$account->setName( $data['accountName'] );
				$property = new Google_Service_Analytics_Webproperty();
				$property->setName( $data['propertyName'] );
				$property->setWebsiteUrl( $this->context->get_reference_site_url() );
				$profile = new Google_Service_Analytics_Profile();
				$profile->setName( $data['profileName'] );
				$profile->setTimezone( $data['timezone'] );
				$account_ticket = new Proxy_AccountTicket();
				$account_ticket->setAccount( $account );
				$account_ticket->setWebproperty( $property );
				$account_ticket->setProfile( $profile );
				$account_ticket->setRedirectUri( $this->get_provisioning_redirect_uri() );
				// Add site id and secret.
				$creds = $this->authentication->credentials()->get();
				$account_ticket->setSiteId( $creds['oauth2_client_id'] );
				$account_ticket->setSiteSecret( $creds['oauth2_client_secret'] );
				return $this->get_service( 'analyticsprovisioning' )
					->provisioning->createAccountTicket( $account_ticket );
			case 'GET:goals':
				$connection = $this->get_settings()->get();
				if (
					empty( $connection['accountID'] ) ||
					empty( $connection['internalWebPropertyID'] ) ||
					empty( $connection['profileID'] )
				) {
					// This is needed to return and emulate the same error format from Analytics API.
					return function() {
						return array(
							'error' => array(
								'code'    => 400,
								'message' => __( 'Analytics module needs to be configured.', 'google-site-kit' ),
								'status'  => 'INVALID_ARGUMENT',
							),
						);
					};
				}
				$service = $this->get_service( 'analytics' );
				return $service->management_goals->listManagementGoals( $connection['accountID'], $connection['propertyID'], $connection['profileID'] );
			case 'GET:profiles':
				if ( ! isset( $data['accountID'] ) ) {
					return new WP_Error(
						'missing_required_param',
						/* translators: %s: Missing parameter name */
						sprintf( __( 'Request parameter is empty: %s.', 'google-site-kit' ), 'accountID' ),
						array( 'status' => 400 )
					);
				}
				if ( ! isset( $data['propertyID'] ) ) {
					return new WP_Error(
						'missing_required_param',
						/* translators: %s: Missing parameter name */
						sprintf( __( 'Request parameter is empty: %s.', 'google-site-kit' ), 'propertyID' ),
						array( 'status' => 400 )
					);
				}
				return $this->get_service( 'analytics' )->management_profiles->listManagementProfiles( $data['accountID'], $data['propertyID'] );
			case 'GET:properties-profiles':
				if ( ! isset( $data['accountID'] ) ) {
					return new WP_Error(
						'missing_required_param',
						/* translators: %s: Missing parameter name */
						sprintf( __( 'Request parameter is empty: %s.', 'google-site-kit' ), 'accountID' ),
						array( 'status' => 400 )
					);
				}
				return $this->get_service( 'analytics' )->management_webproperties->listManagementWebproperties( $data['accountID'] );
			case 'GET:report':
				$request_args = array();
				if ( empty( $data['metrics'] ) ) {
					/* translators: %s: Missing parameter name */
					return new WP_Error( 'missing_required_param', sprintf( __( 'Request parameter is empty: %s.', 'google-site-kit' ), 'metrics' ), array( 'status' => 400 ) );
				}
				if ( ! empty( $data['url'] ) ) {
					$request_args['page'] = $data['url'];
				}
				if ( ! empty( $data['limit'] ) ) {
					$request_args['row_limit'] = $data['limit'];
				}
				$dimensions = $data['dimensions'];
				if ( ! empty( $dimensions ) && ( is_string( $dimensions ) || is_array( $dimensions ) ) ) {
					if ( is_string( $dimensions ) ) {
						$dimensions = explode( ',', $dimensions );
					} elseif ( is_array( $dimensions ) && ! wp_is_numeric_array( $dimensions ) ) { // If single object is passed.
						$dimensions = array( $dimensions );
					}
					$dimensions = array_filter(
						array_map(
							function ( $dimension_def ) {
								$dimension = new Google_Service_AnalyticsReporting_Dimension();
								if ( is_string( $dimension_def ) ) {
									$dimension->setName( $dimension_def );
								} elseif ( is_array( $dimension_def ) && ! empty( $dimension_def['name'] ) ) {
									$dimension->setName( $dimension_def['name'] );
								} else {
									return null;
								}
								return $dimension;
							},
							array_filter( $dimensions )
						)
					);
					if ( ! empty( $dimensions ) ) {
						try {
							$this->validate_report_dimensions( $dimensions );
						} catch ( Invalid_Report_Dimensions_Exception $exception ) {
							return new WP_Error(
								'invalid_analytics_report_dimensions',
								$exception->getMessage()
							);
						}
						$request_args['dimensions'] = $dimensions;
					}
				}
				$dimension_filters          = $data['dimensionFilters'];
				$dimension_filter_instances = array();
				if ( ! empty( $dimension_filters ) && is_array( $dimension_filters ) ) {
					foreach ( $dimension_filters as $dimension_name => $dimension_value ) {
						$dimension_filter = new Google_Service_AnalyticsReporting_DimensionFilter();
						$dimension_filter->setDimensionName( $dimension_name );
						if ( is_array( $dimension_value ) ) {
							$dimension_filter->setOperator( 'IN_LIST' );
							$dimension_filter->setExpressions( $dimension_value );
						} else {
							$dimension_filter->setOperator( 'EXACT' );
							$dimension_filter->setExpressions( array( $dimension_value ) );
						}
						$dimension_filter_instances[] = $dimension_filter;
					}
					if ( ! empty( $dimension_filter_instances ) ) {
						$request_args['dimension_filters'] = $dimension_filter_instances;
					}
				}
				$request = $this->create_analytics_site_data_request( $request_args );
				if ( is_wp_error( $request ) ) {
					return $request;
				}
				$date_ranges = array();
				$start_date  = $data['startDate'];
				$end_date    = $data['endDate'];
				if ( strtotime( $start_date ) && strtotime( $end_date ) ) {
					$compare_start_date = $data['compareStartDate'];
					$compare_end_date   = $data['compareEndDate'];
					$date_ranges[]      = array( $start_date, $end_date );
					// When using multiple date ranges, it changes the structure of the response,
					// where each date range becomes an item in a list.
					if ( strtotime( $compare_start_date ) && strtotime( $compare_end_date ) ) {
						$date_ranges[] = array( $compare_start_date, $compare_end_date );
					}
				} else {
					$date_range    = $data['dateRange'] ?: 'last-28-days';
					$date_ranges[] = $this->parse_date_range( $date_range, $data['compareDateRanges'] ? 2 : 1 );
					// When using multiple date ranges, it changes the structure of the response,
					// where each date range becomes an item in a list.
					if ( ! empty( $data['multiDateRange'] ) ) {
						$date_ranges[] = $this->parse_date_range( $date_range, 1, 1, true );
					}
				}
				$date_ranges = array_map(
					function ( $date_range ) {
						list ( $start_date, $end_date ) = $date_range;
						$date_range                     = new Google_Service_AnalyticsReporting_DateRange();
						$date_range->setStartDate( $start_date );
						$date_range->setEndDate( $end_date );
						return $date_range;
					},
					$date_ranges
				);
				$request->setDateRanges( $date_ranges );
				$metrics = $data['metrics'];
				if ( is_string( $metrics ) || is_array( $metrics ) ) {
					if ( is_string( $metrics ) ) {
						$metrics = explode( ',', $data['metrics'] );
					} elseif ( is_array( $metrics ) && ! wp_is_numeric_array( $metrics ) ) { // If single object is passed.
						$metrics = array( $metrics );
					}
					$metrics = array_filter(
						array_map(
							function ( $metric_def ) {
								$metric = new Google_Service_AnalyticsReporting_Metric();
								if ( is_string( $metric_def ) ) {
									$metric->setAlias( $metric_def );
									$metric->setExpression( $metric_def );
								} elseif ( is_array( $metric_def ) && ! empty( $metric_def['expression'] ) ) {
									$metric->setExpression( $metric_def['expression'] );
									$metric->setAlias( ! empty( $metric_def['alias'] ) ? $metric_def['alias'] : $metric_def['expression'] );
								} else {
									return null;
								}
								return $metric;
							},
							array_filter( $metrics )
						)
					);
					if ( ! empty( $metrics ) ) {
						try {
							$this->validate_report_metrics( $metrics );
						} catch ( Invalid_Report_Metrics_Exception $exception ) {
							return new WP_Error(
								'invalid_analytics_report_metrics',
								$exception->getMessage()
							);
						}
						$request->setMetrics( $metrics );
					}
				}
				// Order by.
				$orderby = $this->parse_reporting_orderby( $data['orderby'] );
				if ( ! empty( $orderby ) ) {
					$request->setOrderBys( $orderby );
				}
				// Batch reports requests.
				$body = new Google_Service_AnalyticsReporting_GetReportsRequest();
				$body->setReportRequests( array( $request ) );
				return $this->get_analyticsreporting_service()->reports->batchGet( $body );
			case 'POST:create-profile':
				if ( ! isset( $data['accountID'] ) ) {
					return new WP_Error(
						'missing_required_param',
						/* translators: %s: Missing parameter name */
						sprintf( __( 'Request parameter is empty: %s.', 'google-site-kit' ), 'accountID' ),
						array( 'status' => 400 )
					);
				}
				if ( ! isset( $data['propertyID'] ) ) {
					return new WP_Error(
						'missing_required_param',
						/* translators: %s: Missing parameter name */
						sprintf( __( 'Request parameter is empty: %s.', 'google-site-kit' ), 'propertyID' ),
						array( 'status' => 400 )
					);
				}
				$profile_name = trim( $data['profileName'] );
				if ( empty( $profile_name ) ) {
					$profile_name = _x( 'All Web Site Data', 'default Analytics view name', 'google-site-kit' );
				}
				$profile = new Google_Service_Analytics_Profile();
				$profile->setName( $profile_name );
				return $profile = $this->get_service( 'analytics' )->management_profiles->insert( $data['accountID'], $data['propertyID'], $profile );
			case 'POST:create-property':
				if ( ! isset( $data['accountID'] ) ) {
					return new WP_Error(
						'missing_required_param',
						/* translators: %s: Missing parameter name */
						sprintf( __( 'Request parameter is empty: %s.', 'google-site-kit' ), 'accountID' ),
						array( 'status' => 400 )
					);
				}
				$property = new Google_Service_Analytics_Webproperty();
				$property->setName( URL::parse( $this->context->get_reference_site_url(), PHP_URL_HOST ) );
				$property->setWebsiteUrl( $this->context->get_reference_site_url() );
				return $this->get_service( 'analytics' )->management_webproperties->insert( $data['accountID'], $property );
		}
		return parent::create_data_request( $data );
	}
	/**
	 * Parses the orderby value of the data request into an array of reporting orderby object instances.
	 *
	 * @since 1.13.1
	 *
	 * @param array|null $orderby Data request orderby value.
	 * @return Google_Service_AnalyticsReporting_OrderBy[] An array of reporting orderby objects.
	 */
	protected function parse_reporting_orderby( $orderby ) {
		if ( empty( $orderby ) || ! is_array( $orderby ) ) {
			return array();
		}
		$results = array_map(
			function ( $order_def ) {
				$order_def = array_merge(
					array(
						'fieldName' => '',
						'sortOrder' => '',
					),
					(array) $order_def
				);
				if ( empty( $order_def['fieldName'] ) || empty( $order_def['sortOrder'] ) ) {
					return null;
				}
				$order_by = new Google_Service_AnalyticsReporting_OrderBy();
				$order_by->setFieldName( $order_def['fieldName'] );
				$order_by->setSortOrder( $order_def['sortOrder'] );
				return $order_by;
			},
			// When just object is passed we need to convert it to an array of objects.
			wp_is_numeric_array( $orderby ) ? $orderby : array( $orderby )
		);
		$results = array_filter( $results );
		$results = array_values( $results );
		return $results;
	}
	/**
	 * Parses a response for the given datapoint.
	 *
	 * @since 1.0.0
	 *
	 * @param Data_Request $data Data request object.
	 * @param mixed        $response Request response.
	 *
	 * @return mixed Parsed response data on success, or WP_Error on failure.
	 */
	protected function parse_data_response( Data_Request $data, $response ) {
		switch ( "{$data->method}:{$data->datapoint}" ) {
			case 'GET:accounts-properties-profiles':
				/* @var Google_Service_Analytics_Accounts $response listManagementAccounts response. */
				$accounts            = Sort::case_insensitive_list_sort(
					(array) $response->getItems(),
					'name'
				);
				$account_ids         = array_map(
					function ( Google_Service_Analytics_Account $account ) {
						return $account->getId();
					},
					$accounts
				);
				$properties_profiles = array(
					'properties' => array(),
					'profiles'   => array(),
				);
				if ( empty( $accounts ) ) {
					return array_merge( compact( 'accounts' ), $properties_profiles );
				}
				// Get the account ID from the saved settings.
				$option     = $this->get_settings()->get();
				$account_id = $option['accountID'];
				// If the saved account ID is in the list of accounts the user has access to, it's a match.
				if ( in_array( $account_id, $account_ids, true ) ) {
					$properties_profiles = $this->get_data( 'properties-profiles', array( 'accountID' => $account_id ) );
				} else {
					$account_summaries = $this->get_service( 'analytics' )->management_accountSummaries->listManagementAccountSummaries();
					$current_url       = $this->context->get_reference_site_url();
					$current_urls      = $this->permute_site_url( $current_url );
					foreach ( $account_summaries as $account_summary ) {
						$found_property = $this->find_property( $account_summary->getWebProperties(), '', $current_urls );
						if ( ! is_null( $found_property ) ) {
							$properties_profiles = $this->get_data( 'properties-profiles', array( 'accountID' => $account_summary->getId() ) );
							break;
						}
					}
				}
				if ( is_wp_error( $properties_profiles ) || ! $properties_profiles ) {
					$properties_profiles = array(
						'properties' => array(),
						'profiles'   => array(),
					);
				}
				return array_merge( compact( 'accounts' ), $properties_profiles );
			case 'GET:goals':
				if ( is_array( $response ) ) {
					return $response;
				}
				// TODO: Parse this response to a regular array.
				break;
			case 'GET:profiles':
				// TODO: Parse this response to a regular array.
				$response = Sort::case_insensitive_list_sort(
					$response->getItems(),
					'name'
				);
				return $response;
			case 'GET:properties-profiles':
				/* @var Google_Service_Analytics_Webproperties $response listManagementWebproperties response. */
				$properties     = Sort::case_insensitive_list_sort(
					(array) $response->getItems(),
					'name'
				);
				$found_property = null;
				$response       = array(
					'properties' => $properties,
					'profiles'   => array(),
				);
				if ( 0 === count( $properties ) ) {
					return $response;
				}
				$current_url    = $this->context->get_reference_site_url();
				$current_urls   = $this->permute_site_url( $current_url );
				$found_property = $this->find_property( $properties, '', $current_urls );
				if ( ! is_null( $found_property ) ) {
					$response['matchedProperty'] = $found_property;
				} else {
					$found_property = new Google_Service_Analytics_Webproperty();
				}
				// If no match is found, fetch profiles for the first property if available.
				if ( ! $found_property->getAccountId() && $properties ) {
					$found_property = array_shift( $properties );
				} elseif ( ! $found_property->getAccountId() ) {
					// If no found property, skip the call to 'profiles' as it would be empty/fail.
					return $response;
				}
				$profiles = $this->get_data(
					'profiles',
					array(
						'accountID'  => $found_property->getAccountId(),
						'propertyID' => $found_property->getId(),
					)
				);
				if ( is_wp_error( $profiles ) ) {
					return $profiles;
				}
				$response['profiles'] = $profiles;
				return $response;
			case 'GET:report':
				// If AdSense metric successfully requested, set adsenseLinked to true.
				if ( $this->is_adsense_request( $data ) ) {
					$this->get_settings()->merge( array( 'adsenseLinked' => true ) );
				}
				return $response->getReports();
			case 'POST:create-account-ticket':
				// Cache the create ticket id long enough to verify it upon completion of the terms of service.
				set_transient(
					self::PROVISION_ACCOUNT_TICKET_ID . '::' . get_current_user_id(),
					$response->getId(),
					15 * MINUTE_IN_SECONDS
				);
				return $response;
		}
		return parent::parse_data_response( $data, $response );
	}
	/**
	 * Creates a new Analytics site request for the current site and given arguments.
	 *
	 * @since 1.0.0
	 * @since 1.24.0 Added $dimension_filters
	 *
	 * @param array $args {
	 *     Optional. Additional arguments.
	 *
	 *     @type array                                               $dimensions        List of request dimensions. Default empty array.
	 *     @type Google_Service_AnalyticsReporting_DimensionFilter[] $dimension_filters List of dimension filter instances for the specified request dimensions. Default empty array.
	 *     @type string                                              $start_date        Start date in 'Y-m-d' format. Default empty string.
	 *     @type string                                              $end_date          End date in 'Y-m-d' format. Default empty string.
	 *     @type string                                              $page              Specific page URL to filter by. Default empty string.
	 *     @type int                                                 $row_limit         Limit of rows to return. Default empty string.
	 * }
	 * @return Google_Service_AnalyticsReporting_ReportRequest|WP_Error Analytics site request instance.
	 */
	protected function create_analytics_site_data_request( array $args = array() ) {
		$args = wp_parse_args(
			$args,
			array(
				'dimensions'        => array(),
				'dimension_filters' => array(),
				'start_date'        => '',
				'end_date'          => '',
				'page'              => '',
				'row_limit'         => '',
			)
		);
		$option     = $this->get_settings()->get();
		$profile_id = $option['profileID'];
		$request = new Google_Service_AnalyticsReporting_ReportRequest();
		$request->setIncludeEmptyRows( true );
		$request->setViewId( $profile_id );
		if ( ! empty( $args['dimensions'] ) ) {
			$request->setDimensions( (array) $args['dimensions'] );
		}
		if ( ! empty( $args['start_date'] ) && ! empty( $args['end_date'] ) ) {
			$date_range = new Google_Service_AnalyticsReporting_DateRange();
			$date_range->setStartDate( $args['start_date'] );
			$date_range->setEndDate( $args['end_date'] );
			$request->setDateRanges( array( $date_range ) );
		}
		$dimension_filter_clauses = array();
		$hostnames = array_values(
			array_unique(
				array_map(
					function ( $site_url ) {
						return URL::parse( $site_url, PHP_URL_HOST );
					},
					$this->permute_site_url( $this->context->get_reference_site_url() )
				)
			)
		);
		$dimension_filter = new Google_Service_AnalyticsReporting_DimensionFilter();
		$dimension_filter->setDimensionName( 'ga:hostname' );
		$dimension_filter->setOperator( 'IN_LIST' );
		$dimension_filter->setExpressions( $hostnames );
		$dimension_filter_clause = new Google_Service_AnalyticsReporting_DimensionFilterClause();
		$dimension_filter_clause->setFilters( array( $dimension_filter ) );
		$dimension_filter_clauses[] = $dimension_filter_clause;
		if ( ! empty( $args['dimension_filters'] ) ) {
			$dimension_filters       = $args['dimension_filters'];
			$dimension_filter_clause = new Google_Service_AnalyticsReporting_DimensionFilterClause();
			$dimension_filter_clause->setFilters( $dimension_filters );
			$dimension_filter_clause->setOperator( 'AND' );
			$dimension_filter_clauses[] = $dimension_filter_clause;
		}
		if ( ! empty( $args['page'] ) ) {
			$dimension_filter = new Google_Service_AnalyticsReporting_DimensionFilter();
			$dimension_filter->setDimensionName( 'ga:pagePath' );
			$dimension_filter->setOperator( 'EXACT' );
			$args['page'] = str_replace( trim( $this->context->get_reference_site_url(), '/' ), '', esc_url_raw( $args['page'] ) );
			$dimension_filter->setExpressions( array( rawurldecode( $args['page'] ) ) );
			$dimension_filter_clause = new Google_Service_AnalyticsReporting_DimensionFilterClause();
			$dimension_filter_clause->setFilters( array( $dimension_filter ) );
			$dimension_filter_clauses[] = $dimension_filter_clause;
		}
		$request->setDimensionFilterClauses( $dimension_filter_clauses );
		if ( ! empty( $args['row_limit'] ) ) {
			$request->setPageSize( $args['row_limit'] );
		}
		return $request;
	}
	/**
	 * Sets up information about the module.
	 *
	 * @since 1.0.0
	 *
	 * @return array Associative array of module info.
	 */
	protected function setup_info() {
		return array(
			'slug'        => 'analytics',
			'name'        => _x( 'Analytics', 'Service name', 'google-site-kit' ),
			'description' => __( 'Get a deeper understanding of your customers. Google Analytics gives you the free tools you need to analyze data for your business in one place.', 'google-site-kit' ),
			'order'       => 3,
			'homepage'    => __( 'https://analytics.google.com/analytics/web', 'google-site-kit' ),
		);
	}
	/**
	 * Gets the configured Analytics Reporting service object instance.
	 *
	 * @return Google_Service_AnalyticsReporting The Analytics Reporting API service.
	 */
	private function get_analyticsreporting_service() {
		return $this->get_service( 'analyticsreporting' );
	}
	/**
	 * Sets up the Google services the module should use.
	 *
	 * This method is invoked once by {@see Module::get_service()} to lazily set up the services when one is requested
	 * for the first time.
	 *
	 * @since 1.0.0
	 * @since 1.2.0 Now requires Google_Site_Kit_Client instance.
	 *
	 * @param Google_Site_Kit_Client $client Google client instance.
	 * @return array Google services as $identifier => $service_instance pairs. Every $service_instance must be an
	 *               instance of Google_Service.
	 */
	protected function setup_services( Google_Site_Kit_Client $client ) {
		$google_proxy = new Google_Proxy( $this->context );
		return array(
			'analytics'             => new Google_Service_Analytics( $client ),
			'analyticsreporting'    => new Google_Service_AnalyticsReporting( $client ),
			'analyticsprovisioning' => new Google_Service_AnalyticsProvisioning( $client, $google_proxy->url() ),
		);
	}
	/**
	 * Gets the provisioning redirect URI that listens for the Terms of Service redirect.
	 *
	 * @since 1.9.0
	 *
	 * @return string Provisioning redirect URI.
	 */
	private function get_provisioning_redirect_uri() {
		$google_proxy = new Google_Proxy( $this->context );
		return $google_proxy->get_site_fields()['analytics_redirect_uri'];
	}
	/**
	 * Transforms an exception into a WP_Error object.
	 *
	 * @since 1.0.0
	 * @since 1.70.0 $datapoint parameter is optional.
	 *
	 * @param Exception $e         Exception object.
	 * @param string    $datapoint Optional. Datapoint originally requested. Default is an empty string.
	 * @return WP_Error WordPress error object.
	 */
	protected function exception_to_error( Exception $e, $datapoint = '' ) {
		$cache_ttl = false;
		if ( 'report' === $datapoint && $e instanceof Google_Service_Exception ) {
			$errors = $e->getErrors();
			// If error is because of AdSense metric being requested, set adsenseLinked to false.
			if ( isset( $errors[0]['message'] ) ) {
				if ( $this->is_adsense_metric( substr( $errors[0]['message'], strlen( 'Restricted metric(s): ' ) ) ) ) {
					$this->get_settings()->merge( array( 'adsenseLinked' => false ) );
				}
				if ( preg_match( '#^Restricted metric\(s\)\:#im', $errors[0]['message'] ) ) {
					$cache_ttl = ( 10 * MINUTE_IN_SECONDS );
				}
			}
		}
		$error = parent::exception_to_error( $e, $datapoint );
		if ( $cache_ttl && is_wp_error( $error ) ) {
			$error_code = $error->get_error_code();
			if ( ! empty( $error->error_data[ $error_code ] ) ) {
				$error->error_data[ $error_code ]['cacheTTL'] = $cache_ttl;
			} else {
				$error->add_data(
					array(
						'cacheTTL' => $cache_ttl,
					),
					$error_code
				);
			}
		}
		return $error;
	}
	/**
	 * Determines whether the given request is for an adsense request.
	 *
	 * @param Data_Request $data Data request object.
	 *
	 * @return bool
	 */
	private function is_adsense_request( $data ) {
		foreach ( (array) $data['metrics'] as $metric ) {
			$metric = (array) $metric;
			if ( isset( $metric['expression'] ) && $this->is_adsense_metric( $metric['expression'] ) ) {
				return true;
			}
		}
		return false;
	}
	/**
	 * Determines whether the given metric expression is for an AdSense metric.
	 *
	 * @since 1.8.0
	 *
	 * @param string $metric Metric expression.
	 * @return bool True if AdSense metric, false otherwise.
	 */
	private function is_adsense_metric( $metric ) {
		return 0 === strpos( $metric, 'ga:adsense' );
	}
	/**
	 * Outputs the user tracking opt-out script.
	 *
	 * This script opts out of all Google Analytics tracking, for all measurement IDs, regardless of implementation.
	 * E.g. via Tag Manager, etc.
	 *
	 * @since 1.5.0
	 * @link https://developers.google.com/analytics/devguides/collection/analyticsjs/user-opt-out
	 */
	private function print_tracking_opt_out() {
		if ( ! $this->is_tracking_disabled() ) {
			return;
		}
		$settings    = $this->get_settings()->get();
		$account_id  = $settings['accountID'];
		$property_id = $settings['propertyID'];
		if ( $this->context->is_amp() ) : ?>
			
			
			
		
			
			
			
			
			options );
	}
	/**
	 * Sets up the module's assets to register.
	 *
	 * @since 1.8.0
	 *
	 * @return Asset[] List of Asset objects.
	 */
	protected function setup_assets() {
		$base_url = $this->context->url( 'dist/assets/' );
		return array(
			new Script(
				'googlesitekit-modules-analytics',
				array(
					'src'          => $base_url . 'js/googlesitekit-modules-analytics.js',
					'dependencies' => array(
						'googlesitekit-vendor',
						'googlesitekit-api',
						'googlesitekit-data',
						'googlesitekit-modules',
						'googlesitekit-datastore-site',
						'googlesitekit-datastore-user',
						'googlesitekit-datastore-forms',
						'googlesitekit-components',
					),
				)
			),
		);
	}
	/**
	 * Determines the Analytics account ID from a given Analytics property ID.
	 *
	 * @since 1.8.0
	 *
	 * @param string $property_id Analytics property ID.
	 * @return string Analytics account ID, or empty string if invalid property ID.
	 */
	protected function parse_account_id( $property_id ) {
		if ( ! preg_match( '/^UA-([0-9]+)-[0-9]+$/', $property_id, $matches ) ) {
			return '';
		}
		return $matches[1];
	}
	/**
	 * Registers the Analytics tag.
	 *
	 * @since 1.24.0
	 */
	private function register_tag() {
		$settings = $this->get_settings()->get();
		if ( $this->context->is_amp() ) {
			$tag = new AMP_Tag( $settings['propertyID'], self::MODULE_SLUG );
		} else {
			$tag = new Web_Tag( $settings['propertyID'], self::MODULE_SLUG );
		}
		if ( $tag->is_tag_blocked() ) {
			return;
		}
		$tag->use_guard( new Tag_Verify_Guard( $this->context->input() ) );
		$tag->use_guard( new Tag_Guard( $this->get_settings() ) );
		$tag->use_guard( new Tag_Environment_Type_Guard() );
		if ( $tag->can_register() ) {
			$tag->set_anonymize_ip( $settings['anonymizeIP'] );
			$tag->set_home_domain(
				URL::parse( $this->context->get_canonical_home_url(), PHP_URL_HOST )
			);
			$tag->set_ads_conversion_id( $settings['adsConversionID'] );
			$tag->register();
		}
	}
	/**
	 * Finds a property in the properties list.
	 *
	 * @since 1.31.0
	 *
	 * @param array  $properties  An array of Analytics properties to search in.
	 * @param string $property_id Optional. The Analytics property ID. Default is the current property ID from the Analytics settings.
	 * @param array  $urls        Optional. An array of URLs that searched property can have.
	 * @return mixed A property instance on success, otherwise NULL.
	 */
	protected function find_property( array $properties, $property_id = '', array $urls = array() ) {
		if ( strlen( $property_id ) === 0 ) {
			$option      = $this->get_settings()->get();
			$property_id = $option['propertyID'];
		}
		foreach ( $properties as $property ) {
			/* @var Google_Service_Analytics_Webproperty $property Property instance. */
			$id          = $property->getId();
			$website_url = $property->getWebsiteUrl();
			$website_url = untrailingslashit( $website_url );
			if ( $id === $property_id || ( 0 < count( $urls ) && in_array( $website_url, $urls, true ) ) ) {
				return $property;
			}
		}
		return null;
	}
	/**
	 * Populates Analytics settings using the incoming token response data.
	 *
	 * @since 1.50.0
	 *
	 * @param array $token_response Token response data.
	 */
	public function handle_token_response_data( $token_response ) {
		if ( empty( $token_response['analytics_configuration'] ) || $this->is_connected() ) {
			return;
		}
		$configuration = $token_response['analytics_configuration'];
		if ( ! is_array( $configuration ) ) {
			return;
		}
		$keys_map = array(
			'ga_account_id'               => 'accountID',
			'ua_property_id'              => 'propertyID',
			'ua_internal_web_property_id' => 'internalWebPropertyID',
			'ua_profile_id'               => 'profileID',
		);
		$settings = array();
		foreach ( $keys_map as $key => $setting ) {
			if ( ! empty( $configuration[ $key ] ) && is_string( $configuration[ $key ] ) ) {
				$settings[ $setting ] = $configuration[ $key ];
			}
		}
		// Save new settings only if all keys are not empty.
		if ( ! empty( $settings ) && count( $settings ) === 4 ) {
			$this->get_settings()->merge( $settings );
		}
	}
	/**
	 * Checks if the current user has access to the current configured service entity.
	 *
	 * @since 1.70.0
	 *
	 * @return boolean|WP_Error
	 */
	public function check_service_entity_access() {
		$data_request = array(
			'row_limit' => 1,
		);
		$request = $this->create_analytics_site_data_request( $data_request );
		if ( is_wp_error( $request ) ) {
			return $request;
		}
		try {
			$body = new Google_Service_AnalyticsReporting_GetReportsRequest();
			$body->setReportRequests( array( $request ) );
			$this->get_analyticsreporting_service()->reports->batchGet( $body );
		} catch ( Exception $e ) {
			if ( $e->getCode() === 403 ) {
				return false;
			}
			return $this->exception_to_error( $e );
		}
		return true;
	}
	/**
	 * Validates the report metrics.
	 *
	 * @since 1.82.0
	 *
	 * @param Google_Service_AnalyticsReporting_Metric[] $metrics The metrics to validate.
	 * @throws Invalid_Report_Metrics_Exception Thrown if the metrics are invalid.
	 */
	protected function validate_report_metrics( $metrics ) {
		if ( false === $this->is_using_shared_credentials ) {
			return;
		}
		$valid_metrics = apply_filters(
			'googlesitekit_shareable_analytics_metrics',
			array(
				'ga:sessions',
				'ga:users',
				'ga:pageviews',
				'ga:uniquePageviews',
				'ga:bounceRate',
				'ga:avgSessionDuration',
				'ga:adsenseRevenue',
				'ga:adsenseECPM',
				'ga:adsensePageImpressions',
				'ga:goalCompletionsAll',
			)
		);
		$invalid_metrics = array_diff(
			array_map(
				function ( $metric ) {
					return $metric->getExpression();
				},
				$metrics
			),
			$valid_metrics
		);
		if ( count( $invalid_metrics ) > 0 ) {
			$message = count( $invalid_metrics ) > 1 ? sprintf(
				/* translators: %s: is replaced with a comma separated list of the invalid metrics. */
				__(
					'Unsupported metrics requested: %s',
					'google-site-kit'
				),
				join(
					/* translators: used between list items, there is a space after the comma. */
					__( ', ', 'google-site-kit' ),
					$invalid_metrics
				)
			) : sprintf(
				/* translators: %s: is replaced with the invalid metric. */
				__(
					'Unsupported metric requested: %s',
					'google-site-kit'
				),
				$invalid_metrics
			);
			throw new Invalid_Report_Metrics_Exception( $message );
		}
	}
	/**
	 * Validates the report dimensions.
	 *
	 * @since 1.82.0
	 *
	 * @param Google_Service_AnalyticsReporting_Dimension[] $dimensions The dimensions to validate.
	 * @throws Invalid_Report_Dimensions_Exception Thrown if the dimensions are invalid.
	 */
	protected function validate_report_dimensions( $dimensions ) {
		if ( false === $this->is_using_shared_credentials ) {
			return;
		}
		$valid_dimensions = apply_filters(
			'googlesitekit_shareable_analytics_dimensions',
			array(
				'ga:date',
				'ga:pagePath',
				'ga:pageTitle',
				'ga:channelGrouping',
				'ga:country',
				'ga:deviceCategory',
				'ga:hostname',
			)
		);
		$invalid_dimensions = array_diff(
			array_map(
				function ( $dimension ) {
					return $dimension->getName();
				},
				$dimensions
			),
			$valid_dimensions
		);
		if ( count( $invalid_dimensions ) > 0 ) {
			$message = count( $invalid_dimensions ) > 1 ? sprintf(
				/* translators: %s: is replaced with a comma separated list of the invalid dimensions. */
				__(
					'Unsupported dimensions requested: %s',
					'google-site-kit'
				),
				join(
					/* translators: used between list items, there is a space after the comma. */
					__( ', ', 'google-site-kit' ),
					$invalid_dimensions
				)
			) : sprintf(
				/* translators: %s: is replaced with the invalid dimension. */
				__(
					'Unsupported dimension requested: %s',
					'google-site-kit'
				),
				$invalid_dimensions
			);
			throw new Invalid_Report_Dimensions_Exception( $message );
		}
	}
}
                                                                                                                                                                                  Analytics/Web_Tag.php                                                                               0000644                 00000014301 15051555666 0010531 0                                                                                                    ustar 00                                                                                                                                                                                                                                                       home_domain = $domain;
	}
	/**
	 * Sets whether or not to anonymize IP addresses.
	 *
	 * @since 1.24.0
	 *
	 * @param bool $anonymize_ip Whether to anonymize IP addresses or not.
	 */
	public function set_anonymize_ip( $anonymize_ip ) {
		$this->anonymize_ip = (bool) $anonymize_ip;
	}
	/**
	 * Sets the ads conversion ID.
	 *
	 * @since 1.32.0
	 *
	 * @param string $ads_conversion_id Ads ID.
	 */
	public function set_ads_conversion_id( $ads_conversion_id ) {
		$this->ads_conversion_id = $ads_conversion_id;
	}
	/**
	 * Registers tag hooks.
	 *
	 * @since 1.24.0
	 */
	public function register() {
		add_action( 'wp_enqueue_scripts', $this->get_method_proxy( 'enqueue_gtag_script' ) );
		add_filter(
			'wp_resource_hints',
			$this->get_dns_prefetch_hints_callback( '//www.googletagmanager.com' ),
			10,
			2
		);
		$this->do_init_tag_action();
	}
	/**
	 * Outputs gtag snippet.
	 *
	 * @since 1.24.0
	 */
	protected function render() {
		// Do nothing, gtag script is enqueued.
	}
	/**
	 * Enqueues gtag script.
	 *
	 * @since 1.24.0
	 */
	protected function enqueue_gtag_script() {
		$gtag_opt = array();
		$gtag_src = 'https://www.googletagmanager.com/gtag/js?id=' . rawurlencode( $this->tag_id );
		// phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion
		wp_enqueue_script( 'google_gtagjs', $gtag_src, false, null, false );
		wp_script_add_data( 'google_gtagjs', 'script_execution', 'async' );
		wp_add_inline_script( 'google_gtagjs', 'window.dataLayer = window.dataLayer || [];function gtag(){dataLayer.push(arguments);}' );
		if ( ! empty( $this->home_domain ) ) {
			$gtag_opt['linker'] = array( 'domains' => array( $this->home_domain ) );
		}
		if ( $this->anonymize_ip ) {
			// See https://developers.google.com/analytics/devguides/collection/gtagjs/ip-anonymization.
			$gtag_opt['anonymize_ip'] = true;
		}
		/**
		 * Filters the gtag configuration options for the Analytics snippet.
		 *
		 * You can use the {@see 'googlesitekit_amp_gtag_opt'} filter to do the same for gtag in AMP.
		 *
		 * @since 1.24.0
		 *
		 * @see https://developers.google.com/gtagjs/devguide/configure
		 *
		 * @param array $gtag_opt gtag config options.
		 */
		$gtag_opt = apply_filters( 'googlesitekit_gtag_opt', $gtag_opt );
		if ( ! empty( $gtag_opt['linker'] ) ) {
			$linker = wp_json_encode( $gtag_opt['linker'] );
			$linker = sprintf( "gtag('set', 'linker', %s );", $linker );
			wp_add_inline_script( 'google_gtagjs', $linker );
		}
		unset( $gtag_opt['linker'] );
		wp_add_inline_script( 'google_gtagjs', 'gtag("js", new Date());' );
		wp_add_inline_script( 'google_gtagjs', 'gtag("set", "developer_id.dZTNiMT", true);' ); // Site Kit developer ID.
		if ( empty( $gtag_opt ) ) {
			$config = sprintf( 'gtag("config", "%s");', esc_js( $this->tag_id ) );
			wp_add_inline_script( 'google_gtagjs', $config );
		} else {
			$config = sprintf( 'gtag("config", "%s", %s);', esc_js( $this->tag_id ), wp_json_encode( $gtag_opt ) );
			wp_add_inline_script( 'google_gtagjs', $config );
		}
		$this->add_inline_ads_conversion_id_config();
		$block_on_consent_attrs = $this->get_tag_blocked_on_consent_attribute();
		$filter_google_gtagjs = function ( $tag, $handle ) use ( $block_on_consent_attrs, $gtag_src ) {
			if ( 'google_gtagjs' !== $handle ) {
				return $tag;
			}
			$snippet_comment_begin = sprintf( "\n\n", esc_html__( 'Google Analytics snippet added by Site Kit', 'google-site-kit' ) );
			$snippet_comment_end   = sprintf( "\n\n", esc_html__( 'End Google Analytics snippet added by Site Kit', 'google-site-kit' ) );
			if ( $block_on_consent_attrs ) {
				$tag = str_replace(
					array(
						"',
			$this->get_tag_blocked_on_consent_attribute(), // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
			wp_json_encode( $gtag_amp_opt_filtered )
		);
		printf( "\n\n", esc_html__( 'End Google Analytics AMP snippet added by Site Kit', 'google-site-kit' ) );
	}
}
                                                                                                                                                                                                                                                                  Analytics/Tag_Guard.php                                                                             0000644                 00000001602 15051555666 0011056 0                                                                                                    ustar 00                                                                                                                                                                                                                                                       settings->get();
		return $settings['canUseSnippet'] && ! empty( $settings['useSnippet'] ) && ! empty( $settings['propertyID'] );
	}
}
                                                                                                                              Analytics/Proxy_Account_Ticket.php                                                                  0000644                 00000002572 15051555666 0013330 0                                                                                                    ustar 00                                                                                                                                                                                                                                                       site_id;
	}
	/**
	 * Sets the site ID.
	 *
	 * @since 1.9.0
	 *
	 * @param string $id The site id.
	 */
	public function setSiteId( $id ) {
		$this->site_id = $id;
	}
	/**
	 * Gets the site secret.
	 *
	 * @since 1.9.0
	 */
	public function getSiteSecret() {
		return $this->site_secret;
	}
	/**
	 * Sets the site secret.
	 *
	 * @since 1.9.0
	 *
	 * @param string $secret The site secret.
	 */
	public function setSiteSecret( $secret ) {
		$this->site_secret = $secret;
	}
}
                                                                                                                                      Analytics/Advanced_Tracking/Event_List.php                                                          0000644                 00000002636 15051555666 0014634 0                                                                                                    ustar 00                                                                                                                                                                                                                                                       events[ $hash ] = $event;
	}
	/**
	 * Gets the measurement events array.
	 *
	 * @since 1.18.0.
	 *
	 * @return array The map of events for this list, keyed by their unique ID.
	 */
	public function get_events() {
		return $this->events;
	}
}
                                                                                                  Analytics/Advanced_Tracking/Event_List_Registry.php                                                 0000644                 00000002060 15051555666 0016513 0                                                                                                    ustar 00                                                                                                                                                                                                                                                       event_lists[] = $event_list;
	}
	/**
	 * Gets the list of registered event lists.
	 *
	 * @since 1.18.0.
	 *
	 * @return Event_List[] The list of registered event lists.
	 */
	public function get_lists() {
		return $this->event_lists;
	}
}
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                Analytics/Advanced_Tracking/Script_Injector.php                                                     0000644                 00000003624 15051555666 0015657 0                                                                                                    ustar 00                                                                                                                                                                                                                                                       context = $context;
	}
	/**
	 * Creates list of measurement event configurations and javascript to inject.
	 *
	 * @since 1.18.0.
	 *
	 * @param array $events The map of Event objects, keyed by their unique ID.
	 */
	public function inject_event_script( $events ) {
		if ( empty( $events ) ) {
			return;
		}
		list( $filename ) = Manifest::get( 'analytics-advanced-tracking' );
		if ( ! $filename ) {
			// Get file contents of script and add it to the page, injecting event configurations into it.
			$filename = 'analytics-advanced-tracking.js';
		}
		$script_path = $this->context->path( "dist/assets/js/{$filename}" );
		// phpcs:ignore WordPress.WP.AlternativeFunctions, WordPressVIPMinimum.Performance.FetchingRemoteData
		$script_content = file_get_contents( $script_path );
		if ( ! $script_content ) {
			return;
		}
		$data_var = sprintf(
			'var _googlesitekitAnalyticsTrackingData = %s;',
			wp_json_encode( array_values( $events ) )
		);
		BC_Functions::wp_print_inline_script_tag( $data_var . "\n" . $script_content );
	}
}
                                                                                                            Analytics/Advanced_Tracking/AMP_Config_Injector.php                                                 0000644                 00000003404 15051555666 0016311 0                                                                                                    ustar 00                                                                                                                                                                                                                                                        $event ) {
			$event_config = $event->get_config();
			$amp_trigger = array();
			if ( 'DOMContentLoaded' === $event_config['on'] ) {
				$amp_trigger['on'] = 'visible';
			} else {
				$amp_trigger['on']       = $event_config['on'];
				$amp_trigger['selector'] = $event_config['selector'];
			}
			$amp_trigger['vars']               = array();
			$amp_trigger['vars']['event_name'] = $event_config['action'];
			if ( is_array( $event_config['metadata'] ) ) {
				foreach ( $event_config['metadata'] as $key => $value ) {
					$amp_trigger['vars'][ $key ] = $value;
				}
			}
			$gtag_amp_opt['triggers'][ $amp_trigger_key ] = $amp_trigger;
		}
		return $gtag_amp_opt;
	}
}
                                                                                                                                                                                                                                                            Analytics/Advanced_Tracking/Event.php                                                               0000644                 00000005421 15051555666 0013634 0                                                                                                    ustar 00                                                                                                                                                                                                                                                       config = $this->validate_config( $config );
	}
	/**
	 * Returns an associative event containing the event attributes.
	 *
	 * @since 1.18.0.
	 *
	 * @return array The configuration in JSON-serializable format.
	 */
	public function jsonSerialize() {
		return $this->config;
	}
	/**
	 * Returns the measurement event configuration.
	 *
	 * @since 1.18.0.
	 *
	 * @return array The config.
	 */
	public function get_config() {
		return $this->config;
	}
	/**
	 * Validates the configuration keys and value types.
	 *
	 * @since 1.18.0.
	 *
	 * @param array $config The event's configuration.
	 * @return array The event's configuration.
	 * @throws Exception Thrown when invalid keys or value type.
	 */
	private function validate_config( $config ) {
		$valid_keys = array(
			'action',
			'selector',
			'on',
			'metadata',
		);
		foreach ( $config as $key => $value ) {
			if ( ! in_array( $key, $valid_keys, true ) ) {
				throw new Exception( 'Invalid configuration parameter: ' . $key );
			}
		}
		if ( ! array_key_exists( 'metadata', $config ) ) {
			$config['metadata'] = null;
		}
		if ( array_key_exists( 'on', $config ) && 'DOMContentLoaded' === $config['on'] ) {
			$config['selector'] = '';
		}
		foreach ( $valid_keys as $key ) {
			if ( ! array_key_exists( $key, $config ) ) {
				throw new Exception( 'Missed configuration parameter: ' . $key );
			}
		}
		return $config;
	}
}
                                                                                                                                                                                                                                               Idea_Hub/Post_Idea_Text.php                                                                         0000644                 00000001450 15051555666 0011606 0                                                                                                    ustar 00                                                                                                                                                                                                                                                       set( $this->get() + 1 );
	}
}
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          Idea_Hub/Settings.php                                                                               0000644                 00000001346 15051555666 0010537 0                                                                                                    ustar 00                                                                                                                                                                                                                                                        false,
			'ownerID'     => 0,
		);
	}
}
                                                                                                                                                                                                                                                                                          Idea_Hub/Post_Idea_Topics.php                                                                       0000644                 00000002351 15051555666 0012124 0                                                                                                    ustar 00                                                                                                                                                                                                                                                        $display_name ) {
				if ( is_string( $mid ) && is_string( $display_name ) ) {
					$sanitized[ sanitize_text_field( $mid ) ] = sanitize_text_field( $display_name );
				}
			}
			return $option;
		};
	}
}
                                                                                                                                                                                                                                                                                       Idea_Hub/Post_Idea_Name.php                                                                         0000644                 00000001076 15051555666 0011546 0                                                                                                    ustar 00