` tags in the page head. Nothing is * contacted: the services read the tag when the site owner asks them to * verify. Works without an AdvisorPPC account. * * Stored under advisorppc_settings['verification'] as an array of four * strings, keyed google, bing, pinterest and meta. * * @package AdvisorPPC */ /** * Class AdvisorPPC_Verification. */ class AdvisorPPC_Verification { /** * Settings section id. */ const SECTION = 'advisorppc_verification'; /** * Longest token accepted. Every service issues far shorter ones. */ const MAX_LENGTH = 128; /** * Settings key => the meta name that service reads. * * @var string[] */ const META_NAMES = array( 'google' => 'google-site-verification', 'bing' => 'msvalidate.01', 'pinterest' => 'p:domain_verify', 'meta' => 'facebook-domain-verification', ); /** * Attach hooks. Runs on advisorppc_loaded. * * @return void */ public static function init() { add_action( 'wp_head', array( __CLASS__, 'print_tags' ), 1 ); if ( is_admin() ) { add_action( 'admin_init', array( __CLASS__, 'register_section' ), 20 ); add_filter( 'advisorppc_sanitize_settings', array( __CLASS__, 'filter_settings' ), 10, 2 ); } } /** * The stored tokens, every key present. * * @return string[] */ public static function tokens() { $stored = class_exists( 'AdvisorPPC_Options' ) ? AdvisorPPC_Options::get( 'verification', array() ) : array(); if ( ! is_array( $stored ) ) { $stored = array(); } $out = array(); foreach ( array_keys( self::META_NAMES ) as $key ) { $out[ $key ] = isset( $stored[ $key ] ) && is_string( $stored[ $key ] ) ? $stored[ $key ] : ''; } return $out; } /** * Print one meta tag per configured service. Front end only. * * @return void */ public static function print_tags() { if ( is_admin() || is_feed() ) { return; } echo self::tags_html( self::tokens() ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- tags_html() escapes every attribute. } /** * The meta tags for a set of tokens. * * @param array $tokens Settings key => token. * @return string */ public static function tags_html( $tokens ) { $html = ''; foreach ( self::META_NAMES as $key => $name ) { $token = isset( $tokens[ $key ] ) ? self::sanitize_token( $tokens[ $key ] ) : ''; if ( '' === $token ) { continue; } $html .= '' . "\n"; } return $html; } /** * Sanitize one token. * * Accepts either the token itself or the whole meta tag the service * shows, in which case the content attribute is taken. The result holds * only letters, digits, underscore, hyphen and equals. * * @param mixed $value Raw value. * @return string */ public static function sanitize_token( $value ) { if ( ! is_string( $value ) ) { return ''; } $value = trim( $value ); if ( false !== stripos( $value, 'content' ) && preg_match( '/\bcontent\s*=\s*(?:"([^"]*)"|\'([^\']*)\'|([^\s"\'>\/]+))/i', $value, $match ) ) { $value = ''; foreach ( array( 1, 2, 3 ) as $group ) { if ( isset( $match[ $group ] ) && '' !== $match[ $group ] ) { $value = $match[ $group ]; break; } } } elseif ( false !== strpos( $value, '<' ) ) { // A tag with no content attribute carries no token. return ''; } $value = preg_replace( '/[^A-Za-z0-9_=\-]/', '', $value ); if ( ! is_string( $value ) ) { return ''; } return substr( $value, 0, self::MAX_LENGTH ); } /** * Sanitize the submitted verification map. * * @param mixed $posted Submitted values. * @return string[] Every key present. */ public static function sanitize_all( $posted ) { $posted = is_array( $posted ) ? $posted : array(); $out = array(); foreach ( array_keys( self::META_NAMES ) as $key ) { $out[ $key ] = isset( $posted[ $key ] ) ? self::sanitize_token( $posted[ $key ] ) : ''; } return $out; } /** * Fold the verification fields into a settings form save. * * Only when the form actually rendered the fields; a form that did not * show them leaves the stored tokens alone. * * @param mixed $settings Sanitized settings about to be stored. * @param mixed $input Raw submission. * @return mixed */ public static function filter_settings( $settings, $input ) { if ( is_array( $input ) && method_exists( 'AdvisorPPC_Settings', 'submission_allows' ) && ! AdvisorPPC_Settings::submission_allows( 'verification', $input ) ) { return $settings; } if ( is_array( $settings ) && is_array( $input ) && isset( $input['verification'] ) && is_array( $input['verification'] ) ) { $settings['verification'] = self::sanitize_all( $input['verification'] ); } return $settings; } /** * Field copy for the settings screen. * * @return array Settings key => array( title, help ). */ public static function labels() { return array( 'google' => array( 'title' => __( 'Google Search Console', 'advisorppc' ), 'help' => __( 'In Search Console, add a URL-prefix property and choose the HTML tag method. Paste the content value, or the whole tag.', 'advisorppc' ), ), 'bing' => array( 'title' => __( 'Bing Webmaster', 'advisorppc' ), 'help' => __( 'In Bing Webmaster Tools, add your site and choose the HTML Meta Tag option. Paste the content value, or the whole tag.', 'advisorppc' ), ), 'pinterest' => array( 'title' => __( 'Pinterest', 'advisorppc' ), 'help' => __( 'In your Pinterest business account settings, claim your website and choose Add HTML tag. Paste the content value, or the whole tag.', 'advisorppc' ), ), 'meta' => array( 'title' => __( 'Meta domain verification', 'advisorppc' ), 'help' => __( 'In Meta Business Settings, open Brand safety, then Domains, add your domain and choose Meta-tag verification. Paste the content value, or the whole tag.', 'advisorppc' ), ), ); } /** * Register the "Site verification" section on the settings screen. * * @return void */ public static function register_section() { if ( ! class_exists( 'AdvisorPPC_Settings' ) ) { return; } add_settings_section( self::SECTION, __( 'Site verification', 'advisorppc' ), array( __CLASS__, 'render_section' ), // The Tags tab's page, so the section saves with that tab's form. method_exists( 'AdvisorPPC_Settings', 'page_for' ) ? AdvisorPPC_Settings::page_for( 'tags' ) : AdvisorPPC_Settings::PAGE ); } /** * Render the section body. * * @return void */ public static function render_section() { $advisorppc_verification_tokens = self::tokens(); $advisorppc_verification_labels = self::labels(); require ADVISORPPC_PLUGIN_DIR . 'admin/views/verification-card.php'; } } /* * Attach on advisorppc_loaded, like the other modules: the plugin requires * this file and then fires the action. */ add_action( 'advisorppc_loaded', array( 'AdvisorPPC_Verification', 'init' ) );