507 lines
16 KiB
PHP
507 lines
16 KiB
PHP
<?php
|
|
/**
|
|
* Class Google\Site_Kit\Modules\Site_Verification
|
|
*
|
|
* @package Google\Site_Kit
|
|
* @copyright 2021 Google LLC
|
|
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
|
|
* @link https://sitekit.withgoogle.com
|
|
*/
|
|
|
|
namespace Google\Site_Kit\Modules;
|
|
|
|
use Google\Site_Kit\Core\Authentication\Clients\Google_Site_Kit_Client;
|
|
use Google\Site_Kit\Core\Authentication\Verification;
|
|
use Google\Site_Kit\Core\Authentication\Verification_File;
|
|
use Google\Site_Kit\Core\Authentication\Verification_Meta;
|
|
use Google\Site_Kit\Core\Modules\Module;
|
|
use Google\Site_Kit\Core\Modules\Module_With_Scopes;
|
|
use Google\Site_Kit\Core\Modules\Module_With_Scopes_Trait;
|
|
use Google\Site_Kit\Core\REST_API\Exception\Invalid_Datapoint_Exception;
|
|
use Google\Site_Kit\Core\Permissions\Permissions;
|
|
use Google\Site_Kit\Core\REST_API\Data_Request;
|
|
use Google\Site_Kit\Core\Storage\Transients;
|
|
use Google\Site_Kit\Core\Util\Exit_Handler;
|
|
use Google\Site_Kit\Core\Util\Google_URL_Matcher_Trait;
|
|
use Google\Site_Kit\Core\Util\Method_Proxy_Trait;
|
|
use Google\Site_Kit_Dependencies\Google\Service\Exception as Google_Service_Exception;
|
|
use Google\Site_Kit_Dependencies\Google\Service\SiteVerification as Google_Service_SiteVerification;
|
|
use Google\Site_Kit_Dependencies\Google\Service\SiteVerification\SiteVerificationWebResourceGettokenRequest as Google_Service_SiteVerification_SiteVerificationWebResourceGettokenRequest;
|
|
use Google\Site_Kit_Dependencies\Google\Service\SiteVerification\SiteVerificationWebResourceGettokenRequestSite as Google_Service_SiteVerification_SiteVerificationWebResourceGettokenRequestSite;
|
|
use Google\Site_Kit_Dependencies\Google\Service\SiteVerification\SiteVerificationWebResourceResource as Google_Service_SiteVerification_SiteVerificationWebResourceResource;
|
|
use Google\Site_Kit_Dependencies\Google\Service\SiteVerification\SiteVerificationWebResourceResourceSite as Google_Service_SiteVerification_SiteVerificationWebResourceResourceSite;
|
|
use Google\Site_Kit_Dependencies\Psr\Http\Message\RequestInterface;
|
|
use WP_Error;
|
|
use Exception;
|
|
|
|
/**
|
|
* Class representing the Site Verification module.
|
|
*
|
|
* @since 1.0.0
|
|
* @access private
|
|
* @ignore
|
|
*/
|
|
final class Site_Verification extends Module implements Module_With_Scopes {
|
|
use Method_Proxy_Trait;
|
|
use Module_With_Scopes_Trait;
|
|
use Google_URL_Matcher_Trait;
|
|
|
|
/**
|
|
* Module slug name.
|
|
*/
|
|
const MODULE_SLUG = 'site-verification';
|
|
|
|
/**
|
|
* Meta site verification type.
|
|
*/
|
|
const VERIFICATION_TYPE_META = 'META';
|
|
|
|
/**
|
|
* File site verification type.
|
|
*/
|
|
const VERIFICATION_TYPE_FILE = 'FILE';
|
|
|
|
/**
|
|
* Verification meta tag cache key.
|
|
*/
|
|
const TRANSIENT_VERIFICATION_META_TAGS = 'googlesitekit_verification_meta_tags';
|
|
|
|
/**
|
|
* Registers functionality through WordPress hooks.
|
|
*
|
|
* @since 1.0.0
|
|
*/
|
|
public function register() {
|
|
$this->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<token>[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, '<meta ' ) ) {
|
|
$verification_tag = '<meta name="google-site-verification" content="' . esc_attr( $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;
|
|
}
|
|
}
|