update
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Google\Site_Kit\Core\Util\Activation_Flag
|
||||
*
|
||||
* @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\Core\Util;
|
||||
|
||||
use Google\Site_Kit\Context;
|
||||
use Google\Site_Kit\Core\Storage\Options;
|
||||
|
||||
/**
|
||||
* Class handling plugin activation.
|
||||
*
|
||||
* @since 1.10.0
|
||||
* @access private
|
||||
* @ignore
|
||||
*/
|
||||
final class Activation_Flag {
|
||||
const OPTION_SHOW_ACTIVATION_NOTICE = 'googlesitekit_show_activation_notice';
|
||||
const OPTION_NEW_SITE_POSTS = 'googlesitekit_new_site_posts';
|
||||
|
||||
/**
|
||||
* Plugin context.
|
||||
*
|
||||
* @since 1.10.0
|
||||
* @var Context
|
||||
*/
|
||||
private $context;
|
||||
|
||||
/**
|
||||
* Option API instance.
|
||||
*
|
||||
* @since 1.10.0
|
||||
* @var Options
|
||||
*/
|
||||
protected $options;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since 1.10.0
|
||||
*
|
||||
* @param Context $context Plugin context.
|
||||
* @param Options $options Optional. The Option API instance. Default is a new instance.
|
||||
*/
|
||||
public function __construct(
|
||||
Context $context,
|
||||
Options $options = null
|
||||
) {
|
||||
$this->context = $context;
|
||||
$this->options = $options ?: new Options( $this->context );
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers functionality through WordPress hooks.
|
||||
*
|
||||
* @since 1.10.0
|
||||
*/
|
||||
public function register() {
|
||||
add_action(
|
||||
'googlesitekit_activation',
|
||||
function( $network_wide ) {
|
||||
// Set activation flag.
|
||||
$this->set_activation_flag( $network_wide );
|
||||
}
|
||||
);
|
||||
|
||||
add_filter(
|
||||
'googlesitekit_admin_data',
|
||||
function ( $data ) {
|
||||
return $this->inline_js_admin_data( $data );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the flag that the plugin has just been activated.
|
||||
*
|
||||
* @since 1.10.0 Migrated from Activation class.
|
||||
*
|
||||
* @param bool $network_wide Whether the plugin is being activated network-wide.
|
||||
*/
|
||||
public function set_activation_flag( $network_wide ) {
|
||||
if ( $network_wide ) {
|
||||
update_network_option( null, self::OPTION_SHOW_ACTIVATION_NOTICE, '1' );
|
||||
return;
|
||||
}
|
||||
|
||||
update_option( self::OPTION_SHOW_ACTIVATION_NOTICE, '1', false );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the flag that the plugin has just been activated.
|
||||
*
|
||||
* @since 1.10.0 Migrated from Activation class.
|
||||
*
|
||||
* @param bool $network_wide Whether to check the flag network-wide.
|
||||
* @return bool True if just activated, false otherwise.
|
||||
*/
|
||||
public function get_activation_flag( $network_wide ) {
|
||||
if ( $network_wide ) {
|
||||
return (bool) get_network_option( null, self::OPTION_SHOW_ACTIVATION_NOTICE );
|
||||
}
|
||||
|
||||
return (bool) get_option( self::OPTION_SHOW_ACTIVATION_NOTICE );
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the flag that the plugin has just been activated.
|
||||
*
|
||||
* @since 1.10.0 Migrated from Activation class.
|
||||
*
|
||||
* @param bool $network_wide Whether the plugin is being activated network-wide.
|
||||
*/
|
||||
public function delete_activation_flag( $network_wide ) {
|
||||
if ( $network_wide ) {
|
||||
delete_network_option( null, self::OPTION_SHOW_ACTIVATION_NOTICE );
|
||||
return;
|
||||
}
|
||||
|
||||
delete_option( self::OPTION_SHOW_ACTIVATION_NOTICE );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Modifies the admin data to pass to JS.
|
||||
*
|
||||
* @since 1.10.0 Migrated from Activation class.
|
||||
*
|
||||
* @param array $data Inline JS data.
|
||||
* @return array Filtered $data.
|
||||
*/
|
||||
private function inline_js_admin_data( $data ) {
|
||||
$data['newSitePosts'] = $this->options->get( self::OPTION_NEW_SITE_POSTS );
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Google\Site_Kit\Core\Util\Activation_Notice
|
||||
*
|
||||
* @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\Core\Util;
|
||||
|
||||
use Google\Site_Kit\Context;
|
||||
use Google\Site_Kit\Core\Admin\Notice;
|
||||
use Google\Site_Kit\Core\Assets\Assets;
|
||||
use Google\Site_Kit\Core\Util\Requires_Javascript_Trait;
|
||||
|
||||
/**
|
||||
* Class handling plugin activation.
|
||||
*
|
||||
* @since 1.10.0 Renamed from Activation.
|
||||
* @access private
|
||||
* @ignore
|
||||
*/
|
||||
final class Activation_Notice {
|
||||
use Requires_Javascript_Trait;
|
||||
|
||||
/**
|
||||
* Plugin context.
|
||||
*
|
||||
* @since 1.10.0
|
||||
* @var Context
|
||||
*/
|
||||
private $context;
|
||||
|
||||
/**
|
||||
* Activation flag instance.
|
||||
*
|
||||
* @since 1.10.0
|
||||
* @var Activation_Flag
|
||||
*/
|
||||
protected $activation_flag;
|
||||
|
||||
/**
|
||||
* Assets API instance.
|
||||
*
|
||||
* @since 1.10.0
|
||||
* @var Assets
|
||||
*/
|
||||
protected $assets;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since 1.10.0
|
||||
*
|
||||
* @param Context $context Plugin context.
|
||||
* @param Activation_Flag $activation_flag Activation flag instance.
|
||||
* @param Assets $assets Optional. The Assets API instance. Default is a new instance.
|
||||
*/
|
||||
public function __construct(
|
||||
Context $context,
|
||||
Activation_Flag $activation_flag,
|
||||
Assets $assets = null
|
||||
) {
|
||||
$this->context = $context;
|
||||
$this->activation_flag = $activation_flag;
|
||||
$this->assets = $assets ?: new Assets( $this->context );
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers functionality through WordPress hooks.
|
||||
*
|
||||
* @since 1.10.0
|
||||
*/
|
||||
public function register() {
|
||||
add_filter(
|
||||
'googlesitekit_admin_notices',
|
||||
function( $notices ) {
|
||||
$notices[] = $this->get_activation_notice();
|
||||
return $notices;
|
||||
}
|
||||
);
|
||||
|
||||
add_action(
|
||||
'admin_enqueue_scripts',
|
||||
function( $hook_suffix ) {
|
||||
if ( 'plugins.php' !== $hook_suffix || ! $this->activation_flag->get_activation_flag( is_network_admin() ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent the default WordPress "Plugin Activated" notice from rendering.
|
||||
*
|
||||
* @link https://github.com/WordPress/WordPress/blob/e1996633228749cdc2d92bc04cc535d45367bfa4/wp-admin/plugins.php#L569-L570
|
||||
*/
|
||||
unset( $_GET['activate'] ); // phpcs:ignore WordPress.Security.NonceVerification, WordPress.VIP.SuperGlobalInputUsage
|
||||
|
||||
$this->assets->enqueue_asset( 'googlesitekit-admin-css' );
|
||||
$this->assets->enqueue_asset( 'googlesitekit-activation' );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the admin notice indicating that the plugin has just been activated.
|
||||
*
|
||||
* @since 1.10.0
|
||||
*
|
||||
* @return Notice Admin notice instance.
|
||||
*/
|
||||
private function get_activation_notice() {
|
||||
return new Notice(
|
||||
'activated',
|
||||
array(
|
||||
'content' => function() {
|
||||
ob_start();
|
||||
?>
|
||||
<div class="googlesitekit-plugin">
|
||||
<?php $this->render_noscript_html(); ?>
|
||||
|
||||
<div id="js-googlesitekit-activation" class="googlesitekit-activation googlesitekit-activation--loading">
|
||||
<div class="googlesitekit-activation__loading">
|
||||
<div role="progressbar" class="mdc-linear-progress mdc-linear-progress--indeterminate">
|
||||
<div class="mdc-linear-progress__buffering-dots"></div>
|
||||
<div class="mdc-linear-progress__buffer"></div>
|
||||
<div class="mdc-linear-progress__bar mdc-linear-progress__primary-bar">
|
||||
<span class="mdc-linear-progress__bar-inner"></span>
|
||||
</div>
|
||||
<div class="mdc-linear-progress__bar mdc-linear-progress__secondary-bar">
|
||||
<span class="mdc-linear-progress__bar-inner"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
return ob_get_clean();
|
||||
},
|
||||
'type' => Notice::TYPE_SUCCESS,
|
||||
'active_callback' => function( $hook_suffix ) {
|
||||
if ( 'plugins.php' !== $hook_suffix ) {
|
||||
return false;
|
||||
}
|
||||
$network_wide = is_network_admin();
|
||||
$flag = $this->activation_flag->get_activation_flag( $network_wide );
|
||||
if ( $flag ) {
|
||||
// Unset the flag so that the notice only shows once.
|
||||
$this->activation_flag->delete_activation_flag( $network_wide );
|
||||
}
|
||||
return $flag;
|
||||
},
|
||||
'dismissible' => true,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Google\Site_Kit\Core\Util\Auto_Updates
|
||||
*
|
||||
* @package Google\Site_Kit\Core\Util
|
||||
* @copyright 2022 Google LLC
|
||||
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
|
||||
* @link https://sitekit.withgoogle.com
|
||||
*/
|
||||
|
||||
namespace Google\Site_Kit\Core\Util;
|
||||
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* Utility class for auto-updates settings.
|
||||
*
|
||||
* @since 1.93.0
|
||||
* @access private
|
||||
* @ignore
|
||||
*/
|
||||
class Auto_Updates {
|
||||
|
||||
/**
|
||||
* Auto updated forced enabled.
|
||||
*
|
||||
* @since 1.93.0
|
||||
* @var true
|
||||
*/
|
||||
const AUTO_UPDATE_FORCED_ENABLED = true;
|
||||
|
||||
/**
|
||||
* Auto updated forced disabled.
|
||||
*
|
||||
* @since 1.93.0
|
||||
* @var false
|
||||
*/
|
||||
const AUTO_UPDATE_FORCED_DISABLED = false;
|
||||
|
||||
/**
|
||||
* Auto updated not forced.
|
||||
*
|
||||
* @since 1.93.0
|
||||
* @var false
|
||||
*/
|
||||
const AUTO_UPDATE_NOT_FORCED = null;
|
||||
|
||||
/**
|
||||
* Checks whether plugin auto-updates are enabled for the site.
|
||||
*
|
||||
* @since 1.93.0
|
||||
*
|
||||
* @return bool `false` if auto-updates are disabled, `true` otherwise.
|
||||
*/
|
||||
public static function is_plugin_autoupdates_enabled() {
|
||||
if ( self::AUTO_UPDATE_FORCED_DISABLED === self::sitekit_forced_autoupdates_status() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( function_exists( 'wp_is_auto_update_enabled_for_type' ) ) {
|
||||
return wp_is_auto_update_enabled_for_type( 'plugin' );
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the site has auto updates enabled for Site Kit.
|
||||
*
|
||||
* @since 1.93.0
|
||||
*
|
||||
* @return bool `true` if auto updates are enabled, otherwise `false`.
|
||||
*/
|
||||
public static function is_sitekit_autoupdates_enabled() {
|
||||
if ( self::AUTO_UPDATE_FORCED_ENABLED === self::sitekit_forced_autoupdates_status() ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( self::AUTO_UPDATE_FORCED_DISABLED === self::sitekit_forced_autoupdates_status() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$enabled_auto_updates = (array) get_site_option( 'auto_update_plugins', array() );
|
||||
|
||||
if ( ! $enabled_auto_updates ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if the Site Kit is in the list of auto-updated plugins.
|
||||
return in_array( GOOGLESITEKIT_PLUGIN_BASENAME, $enabled_auto_updates, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether auto-updates are forced for Site Kit.
|
||||
*
|
||||
* @since 1.93.0
|
||||
*
|
||||
* @return bool|null
|
||||
*/
|
||||
public static function sitekit_forced_autoupdates_status() {
|
||||
if ( ! function_exists( 'wp_is_auto_update_forced_for_item' ) ) {
|
||||
return self::AUTO_UPDATE_NOT_FORCED;
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'get_plugin_data' ) ) {
|
||||
require_once ABSPATH . 'wp-admin/includes/plugin.php';
|
||||
}
|
||||
|
||||
$sitekit_plugin_data = get_plugin_data( GOOGLESITEKIT_PLUGIN_MAIN_FILE );
|
||||
$sitekit_update_data = self::get_sitekit_update_data();
|
||||
$item = (object) array_merge( $sitekit_plugin_data, $sitekit_update_data );
|
||||
|
||||
$is_auto_update_forced_for_sitekit = wp_is_auto_update_forced_for_item( 'plugin', null, $item );
|
||||
|
||||
if ( true === $is_auto_update_forced_for_sitekit ) {
|
||||
return self::AUTO_UPDATE_FORCED_ENABLED;
|
||||
}
|
||||
|
||||
if ( false === $is_auto_update_forced_for_sitekit ) {
|
||||
return self::AUTO_UPDATE_FORCED_DISABLED;
|
||||
}
|
||||
|
||||
return self::AUTO_UPDATE_NOT_FORCED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges plugin update data in the site transient with some default plugin data.
|
||||
*
|
||||
* @since 1.113.0
|
||||
*
|
||||
* @return array Site Kit plugin update data.
|
||||
*/
|
||||
protected static function get_sitekit_update_data() {
|
||||
$sitekit_update_data = array(
|
||||
'id' => 'w.org/plugins/' . dirname( GOOGLESITEKIT_PLUGIN_BASENAME ),
|
||||
'slug' => dirname( GOOGLESITEKIT_PLUGIN_BASENAME ),
|
||||
'plugin' => GOOGLESITEKIT_PLUGIN_BASENAME,
|
||||
'new_version' => '',
|
||||
'url' => '',
|
||||
'package' => '',
|
||||
'icons' => array(),
|
||||
'banners' => array(),
|
||||
'banners_rtl' => array(),
|
||||
'tested' => '',
|
||||
'requires_php' => GOOGLESITEKIT_PHP_MINIMUM,
|
||||
'compatibility' => new stdClass(),
|
||||
);
|
||||
|
||||
$plugin_updates = get_site_transient( 'update_plugins' );
|
||||
|
||||
$transient_data = array();
|
||||
|
||||
if ( isset( $plugin_updates->noupdate[ GOOGLESITEKIT_PLUGIN_BASENAME ] ) ) {
|
||||
$transient_data = $plugin_updates->noupdate[ GOOGLESITEKIT_PLUGIN_BASENAME ];
|
||||
}
|
||||
|
||||
if ( isset( $plugin_updates->response[ GOOGLESITEKIT_PLUGIN_BASENAME ] ) ) {
|
||||
$transient_data = $plugin_updates->response[ GOOGLESITEKIT_PLUGIN_BASENAME ];
|
||||
}
|
||||
|
||||
return array_merge( $sitekit_update_data, (array) $transient_data );
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Google\Site_Kit\Core\Util\BC_Functions
|
||||
*
|
||||
* @package Google\Site_Kit\Core\Util
|
||||
* @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\Core\Util;
|
||||
|
||||
use BadMethodCallException;
|
||||
use WP_REST_Request;
|
||||
|
||||
/**
|
||||
* Class for providing backwards compatible core functions, without polyfilling.
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @access private
|
||||
* @ignore
|
||||
*/
|
||||
class BC_Functions {
|
||||
|
||||
/**
|
||||
* Proxies calls to global functions, while falling back to the internal method by the same name.
|
||||
*
|
||||
* @since 1.7.0
|
||||
*
|
||||
* @param string $function_name Function name to call.
|
||||
* @param array $arguments Arguments passed to function.
|
||||
* @return mixed
|
||||
* @throws BadMethodCallException Thrown if no method exists by the same name as the function.
|
||||
*/
|
||||
public static function __callStatic( $function_name, $arguments ) {
|
||||
if ( function_exists( $function_name ) ) {
|
||||
return call_user_func_array( $function_name, $arguments );
|
||||
}
|
||||
|
||||
if ( method_exists( __CLASS__, $function_name ) ) {
|
||||
return self::{ $function_name }( ...$arguments );
|
||||
}
|
||||
|
||||
throw new BadMethodCallException( "$function_name does not exist." );
|
||||
}
|
||||
|
||||
/**
|
||||
* Basic implementation of the wp_sanitize_script_attributes function introduced in the WordPress version 5.7.0.
|
||||
*
|
||||
* @since 1.41.0
|
||||
*
|
||||
* @param array $attributes Key-value pairs representing `<script>` tag attributes.
|
||||
* @return string String made of sanitized `<script>` tag attributes.
|
||||
*/
|
||||
protected static function wp_sanitize_script_attributes( $attributes ) {
|
||||
$attributes_string = '';
|
||||
|
||||
foreach ( $attributes as $attribute_name => $attribute_value ) {
|
||||
if ( is_bool( $attribute_value ) ) {
|
||||
if ( $attribute_value ) {
|
||||
$attributes_string .= ' ' . esc_attr( $attribute_name );
|
||||
}
|
||||
} else {
|
||||
$attributes_string .= sprintf( ' %1$s="%2$s"', esc_attr( $attribute_name ), esc_attr( $attribute_value ) );
|
||||
}
|
||||
}
|
||||
|
||||
return $attributes_string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A fallback for the wp_get_script_tag function introduced in the WordPress version 5.7.0.
|
||||
*
|
||||
* @since 1.41.0
|
||||
*
|
||||
* @param array $attributes Key-value pairs representing `<script>` tag attributes.
|
||||
* @return string String containing `<script>` opening and closing tags.
|
||||
*/
|
||||
protected static function wp_get_script_tag( $attributes ) {
|
||||
return sprintf( "<script %s></script>\n", self::wp_sanitize_script_attributes( $attributes ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* A fallback for the wp_print_script_tag function introduced in the WordPress version 5.7.0.
|
||||
*
|
||||
* @since 1.41.0
|
||||
*
|
||||
* @param array $attributes Key-value pairs representing `<script>` tag attributes.
|
||||
*/
|
||||
protected static function wp_print_script_tag( $attributes ) {
|
||||
echo self::wp_get_script_tag( $attributes ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
/**
|
||||
* A fallback for the wp_get_inline_script_tag function introduced in the WordPress version 5.7.0.
|
||||
*
|
||||
* @since 1.41.0
|
||||
*
|
||||
* @param string $javascript Inline JavaScript code.
|
||||
* @param array $attributes Optional. Key-value pairs representing `<script>` tag attributes.
|
||||
* @return string String containing inline JavaScript code wrapped around `<script>` tag.
|
||||
*/
|
||||
protected static function wp_get_inline_script_tag( $javascript, $attributes = array() ) {
|
||||
$javascript = "\n" . trim( $javascript, "\n\r " ) . "\n";
|
||||
return sprintf( "<script%s>%s</script>\n", self::wp_sanitize_script_attributes( $attributes ), $javascript );
|
||||
}
|
||||
|
||||
/**
|
||||
* A fallback for the wp_get_inline_script_tag function introduced in the WordPress version 5.7.0.
|
||||
*
|
||||
* @since 1.41.0
|
||||
*
|
||||
* @param string $javascript Inline JavaScript code.
|
||||
* @param array $attributes Optional. Key-value pairs representing `<script>` tag attributes.
|
||||
*/
|
||||
protected static function wp_print_inline_script_tag( $javascript, $attributes = array() ) {
|
||||
echo self::wp_get_inline_script_tag( $javascript, $attributes ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
/**
|
||||
* A fallback for the wp_get_sidebar function introduced in the WordPress version 5.9.0.
|
||||
*
|
||||
* Retrieves the registered sidebar with the given ID.
|
||||
*
|
||||
* @since 1.86.0
|
||||
*
|
||||
* @global array $wp_registered_sidebars The registered sidebars.
|
||||
*
|
||||
* @param string $id The sidebar ID.
|
||||
* @return array|null The discovered sidebar, or null if it is not registered.
|
||||
*/
|
||||
protected static function wp_get_sidebar( $id ) {
|
||||
global $wp_registered_sidebars;
|
||||
|
||||
foreach ( (array) $wp_registered_sidebars as $sidebar ) {
|
||||
if ( $sidebar['id'] === $id ) {
|
||||
return $sidebar;
|
||||
}
|
||||
}
|
||||
|
||||
if ( 'wp_inactive_widgets' === $id ) {
|
||||
return array(
|
||||
'id' => 'wp_inactive_widgets',
|
||||
'name' => __( 'Inactive widgets', 'default' ),
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Google\Site_Kit\Core\Util\Collection_Key_Cap_Filter
|
||||
*
|
||||
* @package Google\Site_Kit\Core\Util
|
||||
* @copyright 2022 Google LLC
|
||||
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
|
||||
* @link https://sitekit.withgoogle.com
|
||||
*/
|
||||
|
||||
namespace Google\Site_Kit\Core\Util;
|
||||
|
||||
/**
|
||||
* Class for filtering a specific key of a collection based on a capability.
|
||||
*
|
||||
* @since 1.77.0
|
||||
* @access private
|
||||
* @ignore
|
||||
*/
|
||||
class Collection_Key_Cap_Filter {
|
||||
|
||||
/**
|
||||
* Collection key.
|
||||
*
|
||||
* @since 1.77.0
|
||||
* @var string
|
||||
*/
|
||||
private $key;
|
||||
|
||||
/**
|
||||
* Capability.
|
||||
*
|
||||
* @since 1.77.0
|
||||
* @var string
|
||||
*/
|
||||
private $cap;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since 1.77.0.
|
||||
*
|
||||
* @param string $key Target collection key to filter.
|
||||
* @param string $capability Required capability to filter by.
|
||||
*/
|
||||
public function __construct( $key, $capability ) {
|
||||
$this->key = $key;
|
||||
$this->cap = $capability;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the given value of a specific key in each item of the given collection
|
||||
* based on the key and capability.
|
||||
*
|
||||
* @since 1.77.0
|
||||
*
|
||||
* @param array[] $collection Array of arrays.
|
||||
* @return array[] Filtered collection.
|
||||
*/
|
||||
public function filter_key_by_cap( array $collection ) {
|
||||
foreach ( $collection as $meta_arg => &$value ) {
|
||||
if ( ! current_user_can( $this->cap, $meta_arg ) ) {
|
||||
unset( $value[ $this->key ] );
|
||||
}
|
||||
}
|
||||
return $collection;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Google\Site_Kit\Core\Util\URL
|
||||
*
|
||||
* @package Google\Site_Kit\Core\Util
|
||||
* @copyright 2023 Google LLC
|
||||
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
|
||||
* @link https://sitekit.withgoogle.com
|
||||
*/
|
||||
|
||||
namespace Google\Site_Kit\Core\Util;
|
||||
|
||||
/**
|
||||
* Class for custom date parsing methods.
|
||||
*
|
||||
* @since 1.99.0
|
||||
* @access private
|
||||
* @ignore
|
||||
*/
|
||||
class Date {
|
||||
|
||||
/**
|
||||
* Parses a date range string into a start date and an end date.
|
||||
*
|
||||
* @since 1.99.0
|
||||
*
|
||||
* @param string $range Date range string. Either 'last-7-days', 'last-14-days', 'last-90-days', or
|
||||
* 'last-28-days' (default).
|
||||
* @param string $multiplier Optional. How many times the date range to get. This value can be specified if the
|
||||
* range should be request multiple times back. Default 1.
|
||||
* @param int $offset Days the range should be offset by. Default 1. Used by Search Console where
|
||||
* data is delayed by two days.
|
||||
* @param bool $previous Whether to select the previous period. Default false.
|
||||
* @return array List with two elements, the first with the start date and the second with the end date, both as 'Y-m-d'.
|
||||
*/
|
||||
public static function parse_date_range( $range, $multiplier = 1, $offset = 1, $previous = false ) {
|
||||
preg_match( '*-(\d+)-*', $range, $matches );
|
||||
$number_of_days = $multiplier * ( isset( $matches[1] ) ? $matches[1] : 28 );
|
||||
|
||||
// Calculate the end date. For previous period requests, offset period by the number of days in the request.
|
||||
$end_date_offset = $previous ? $offset + $number_of_days : $offset;
|
||||
$date_end = gmdate( 'Y-m-d', strtotime( $end_date_offset . ' days ago' ) );
|
||||
|
||||
// Set the start date.
|
||||
$start_date_offset = $end_date_offset + $number_of_days - 1;
|
||||
$date_start = gmdate( 'Y-m-d', strtotime( $start_date_offset . ' days ago' ) );
|
||||
|
||||
return array( $date_start, $date_end );
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Google\Site_Kit\Core\Util\DeveloperPluginInstaller
|
||||
*
|
||||
* @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\Core\Util;
|
||||
|
||||
use Google\Site_Kit\Context;
|
||||
use Google\Site_Kit\Core\Permissions\Permissions;
|
||||
use Google\Site_Kit\Core\REST_API\REST_Route;
|
||||
use WP_REST_Server;
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Response;
|
||||
|
||||
/**
|
||||
* Class responsible for providing the helper plugin via the automatic updater.
|
||||
*
|
||||
* @since 1.3.0
|
||||
*/
|
||||
class Developer_Plugin_Installer {
|
||||
|
||||
const SLUG = 'google-site-kit-dev-settings';
|
||||
|
||||
/**
|
||||
* Plugin context.
|
||||
*
|
||||
* @since 1.3.0
|
||||
* @var Context
|
||||
*/
|
||||
private $context;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since 1.3.0
|
||||
*
|
||||
* @param Context $context Plugin context.
|
||||
*/
|
||||
public function __construct( Context $context ) {
|
||||
$this->context = $context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers functionality through WordPress hooks.
|
||||
*
|
||||
* @since 1.3.0
|
||||
*/
|
||||
public function register() {
|
||||
// Only filter plugins API response if the developer plugin is not already active.
|
||||
if ( ! defined( 'GOOGLESITEKITDEVSETTINGS_VERSION' ) ) {
|
||||
add_filter(
|
||||
'plugins_api',
|
||||
function( $value, $action, $args ) {
|
||||
return $this->plugin_info( $value, $action, $args );
|
||||
},
|
||||
10,
|
||||
3
|
||||
);
|
||||
}
|
||||
|
||||
add_filter(
|
||||
'googlesitekit_rest_routes',
|
||||
function( $routes ) {
|
||||
return array_merge( $routes, $this->get_rest_routes() );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets related REST routes.
|
||||
*
|
||||
* @since 1.3.0
|
||||
*
|
||||
* @return array List of REST_Route objects.
|
||||
*/
|
||||
private function get_rest_routes() {
|
||||
$can_setup = function() {
|
||||
return current_user_can( Permissions::SETUP );
|
||||
};
|
||||
|
||||
return array(
|
||||
new REST_Route(
|
||||
'core/site/data/developer-plugin',
|
||||
array(
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => function( WP_REST_Request $request ) {
|
||||
$is_active = defined( 'GOOGLESITEKITDEVSETTINGS_VERSION' );
|
||||
$installed = $is_active;
|
||||
$slug = self::SLUG;
|
||||
$plugin = "$slug/$slug.php";
|
||||
|
||||
if ( ! $is_active ) {
|
||||
if ( ! function_exists( 'get_plugins' ) ) {
|
||||
require_once ABSPATH . 'wp-admin/includes/plugin.php';
|
||||
}
|
||||
foreach ( array_keys( get_plugins() ) as $installed_plugin ) {
|
||||
if ( $installed_plugin === $plugin ) {
|
||||
$installed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Alternate wp_nonce_url without esc_html breaking query parameters.
|
||||
$nonce_url = function ( $action_url, $action ) {
|
||||
return add_query_arg( '_wpnonce', wp_create_nonce( $action ), $action_url );
|
||||
};
|
||||
$activate_url = $nonce_url( self_admin_url( 'plugins.php?action=activate&plugin=' . $plugin ), 'activate-plugin_' . $plugin );
|
||||
$install_url = $nonce_url( self_admin_url( 'update.php?action=install-plugin&plugin=' . $slug ), 'install-plugin_' . $slug );
|
||||
|
||||
return new WP_REST_Response(
|
||||
array(
|
||||
'active' => $is_active,
|
||||
'installed' => $installed,
|
||||
'activateURL' => current_user_can( 'activate_plugin', $plugin ) ? esc_url_raw( $activate_url ) : false,
|
||||
'installURL' => current_user_can( 'install_plugins' ) ? esc_url_raw( $install_url ) : false,
|
||||
'configureURL' => $is_active ? esc_url_raw( $this->context->admin_url( 'dev-settings' ) ) : false,
|
||||
)
|
||||
);
|
||||
},
|
||||
'permission_callback' => $can_setup,
|
||||
),
|
||||
)
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves plugin information data from the Site Kit REST API.
|
||||
*
|
||||
* @since 1.3.0
|
||||
*
|
||||
* @param false|object|array $value The result object or array. Default false.
|
||||
* @param string $action The type of information being requested from the Plugin Installation API.
|
||||
* @param object $args Plugin API arguments.
|
||||
* @return false|object|array Updated $value, or passed-through $value on failure.
|
||||
*/
|
||||
private function plugin_info( $value, $action, $args ) {
|
||||
if ( 'plugin_information' !== $action || self::SLUG !== $args->slug ) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
$data = $this->fetch_plugin_data();
|
||||
if ( ! $data ) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
$new_data = array(
|
||||
'slug' => self::SLUG,
|
||||
'name' => $data['name'],
|
||||
'version' => $data['version'],
|
||||
'author' => '<a href="https://opensource.google.com">Google</a>',
|
||||
'download_link' => $data['download_url'],
|
||||
'trunk' => $data['download_url'],
|
||||
'tested' => $data['tested'],
|
||||
'requires' => $data['requires'],
|
||||
'requires_php' => $data['requires_php'],
|
||||
'last_updated' => $data['last_updated'],
|
||||
);
|
||||
if ( ! empty( $data['icons'] ) ) {
|
||||
$new_data['icons'] = $data['icons'];
|
||||
}
|
||||
if ( ! empty( $data['banners'] ) ) {
|
||||
$new_data['banners'] = $data['banners'];
|
||||
}
|
||||
if ( ! empty( $data['banners_rtl'] ) ) {
|
||||
$new_data['banners_rtl'] = $data['banners_rtl'];
|
||||
}
|
||||
|
||||
return (object) $new_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets plugin data from the API.
|
||||
*
|
||||
* @since 1.3.0
|
||||
* @since 1.99.0 Update plugin data to pull from GCS bucket.
|
||||
*
|
||||
* @return array|null Associative array of plugin data, or null on failure.
|
||||
*/
|
||||
private function fetch_plugin_data() {
|
||||
// phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.wp_remote_get_wp_remote_get
|
||||
$response = wp_remote_get( 'https://storage.googleapis.com/site-kit-dev-plugins/google-site-kit-dev-settings/updates.json' );
|
||||
|
||||
// Retrieve data from the body and decode json format.
|
||||
return json_decode( wp_remote_retrieve_body( $response ), true );
|
||||
}
|
||||
}
|
||||
166
wp-content/plugins/google-site-kit/includes/Core/Util/Entity.php
Normal file
166
wp-content/plugins/google-site-kit/includes/Core/Util/Entity.php
Normal file
@@ -0,0 +1,166 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Google\Site_Kit\Core\Util\Entity
|
||||
*
|
||||
* @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\Core\Util;
|
||||
|
||||
/**
|
||||
* Class representing an entity.
|
||||
*
|
||||
* An entity in Site Kit terminology is based on a canonical URL, i.e. every
|
||||
* canonical frontend URL has an associated entity.
|
||||
*
|
||||
* An entity may also have a type, if it can be determined.
|
||||
* Possible types are e.g. 'post' for a WordPress post (of any post type!),
|
||||
* 'term' for a WordPress term (of any taxonomy!), 'blog' for the blog archive,
|
||||
* 'date' for a date-based archive etc.
|
||||
*
|
||||
* For specific entity types, the entity will also have a title, and it may
|
||||
* even have an ID. For example:
|
||||
* * For a type of 'post', the entity ID will be the post ID and the entity
|
||||
* title will be the post title.
|
||||
* * For a type of 'term', the entity ID will be the term ID and the entity
|
||||
* title will be the term title.
|
||||
* * For a type of 'date', there will be no entity ID, but the entity title
|
||||
* will be the title of the date-based archive.
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @access private
|
||||
* @ignore
|
||||
*/
|
||||
final class Entity {
|
||||
|
||||
/**
|
||||
* The entity URL.
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @var string
|
||||
*/
|
||||
private $url;
|
||||
|
||||
/**
|
||||
* The entity type.
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @var string
|
||||
*/
|
||||
private $type;
|
||||
|
||||
/**
|
||||
* The entity title.
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @var string
|
||||
*/
|
||||
private $title;
|
||||
|
||||
/**
|
||||
* The entity ID.
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @var int
|
||||
*/
|
||||
private $id;
|
||||
|
||||
/**
|
||||
* Entity URL sub-variant.
|
||||
*
|
||||
* @since 1.42.0
|
||||
* @var string
|
||||
*/
|
||||
private $mode;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since 1.7.0
|
||||
*
|
||||
* @param string $url The entity URL.
|
||||
* @param array $args {
|
||||
* Optional. Additional entity arguments.
|
||||
*
|
||||
* @type string $type The entity type.
|
||||
* @type string $title The entity title.
|
||||
* @type int $id The entity ID.
|
||||
* @type string $mode Entity URL sub-variant.
|
||||
* }
|
||||
*/
|
||||
public function __construct( $url, array $args = array() ) {
|
||||
$args = array_merge(
|
||||
array(
|
||||
'type' => '',
|
||||
'title' => '',
|
||||
'id' => 0,
|
||||
'mode' => '',
|
||||
),
|
||||
$args
|
||||
);
|
||||
|
||||
$this->url = $url;
|
||||
$this->type = (string) $args['type'];
|
||||
$this->title = (string) $args['title'];
|
||||
$this->id = (int) $args['id'];
|
||||
$this->mode = (string) $args['mode'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the entity URL.
|
||||
*
|
||||
* @since 1.7.0
|
||||
*
|
||||
* @return string The entity URL.
|
||||
*/
|
||||
public function get_url() {
|
||||
return $this->url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the entity type.
|
||||
*
|
||||
* @since 1.7.0
|
||||
*
|
||||
* @return string The entity type, or empty string if unknown.
|
||||
*/
|
||||
public function get_type() {
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the entity title.
|
||||
*
|
||||
* @since 1.7.0
|
||||
*
|
||||
* @return string The entity title, or empty string if unknown.
|
||||
*/
|
||||
public function get_title() {
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the entity ID.
|
||||
*
|
||||
* @since 1.7.0
|
||||
*
|
||||
* @return int The entity ID, or 0 if unknown.
|
||||
*/
|
||||
public function get_id() {
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the entity URL sub-variant.
|
||||
*
|
||||
* @since 1.42.0
|
||||
*
|
||||
* @return string The entity title, or empty string if unknown.
|
||||
*/
|
||||
public function get_mode() {
|
||||
return $this->mode;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,676 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Google\Site_Kit\Core\Util\Entity_Factory
|
||||
*
|
||||
* @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\Core\Util;
|
||||
|
||||
use Google\Site_Kit\Context;
|
||||
use Google\Site_Kit\Plugin;
|
||||
use WP_Query;
|
||||
use WP_Post;
|
||||
use WP_Term;
|
||||
use WP_User;
|
||||
use WP_Post_Type;
|
||||
use WP_Screen;
|
||||
|
||||
/**
|
||||
* Class providing access to entities.
|
||||
*
|
||||
* This class entirely relies on WordPress core behavior and is technically decoupled from Site Kit. For example,
|
||||
* entities returned by this factory rely on the regular WordPress home URL and ignore Site Kit-specific details, such
|
||||
* as an alternative "reference site URL".
|
||||
*
|
||||
* Instead of relying on this class directly, use {@see Context::get_reference_entity()} or
|
||||
* {@see Context::get_reference_entity_from_url()}.
|
||||
*
|
||||
* @since 1.15.0
|
||||
* @access private
|
||||
* @ignore
|
||||
*/
|
||||
final class Entity_Factory {
|
||||
|
||||
/**
|
||||
* Gets the entity for the current WordPress context, if available.
|
||||
*
|
||||
* @since 1.15.0
|
||||
*
|
||||
* @return Entity|null The entity for the current context, or null if none could be determined.
|
||||
*/
|
||||
public static function from_context() {
|
||||
global $wp_the_query;
|
||||
|
||||
// If currently in WP admin, run admin-specific checks.
|
||||
if ( is_admin() ) {
|
||||
$screen = get_current_screen();
|
||||
if ( ! $screen instanceof WP_Screen || 'post' !== $screen->base ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$post = get_post();
|
||||
if ( $post instanceof WP_Post && self::is_post_public( $post ) ) {
|
||||
return self::create_entity_for_post( $post, 1 );
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Otherwise, run frontend-specific `WP_Query` logic.
|
||||
if ( $wp_the_query instanceof WP_Query ) {
|
||||
$entity = self::from_wp_query( $wp_the_query );
|
||||
|
||||
$request_uri = Plugin::instance()->context()->input()->filter( INPUT_SERVER, 'REQUEST_URI' );
|
||||
return self::maybe_convert_to_amp_entity( $request_uri, $entity );
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the entity for the given URL, if available.
|
||||
*
|
||||
* Calling this method is expensive, so it should only be used in certain admin contexts where this is acceptable.
|
||||
*
|
||||
* @since 1.15.0
|
||||
*
|
||||
* @param string $url URL to determine the entity from.
|
||||
* @return Entity|null The entity for the URL, or null if none could be determined.
|
||||
*/
|
||||
public static function from_url( $url ) {
|
||||
$query = WP_Query_Factory::from_url( $url );
|
||||
if ( ! $query ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$query->get_posts();
|
||||
|
||||
$entity = self::from_wp_query( $query );
|
||||
|
||||
return self::maybe_convert_to_amp_entity( $url, $entity );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the entity for the given `WP_Query` object, if available.
|
||||
*
|
||||
* @since 1.15.0
|
||||
*
|
||||
* @param WP_Query $query WordPress query object. Must already have run the actual database query.
|
||||
* @return Entity|null The entity for the query, or null if none could be determined.
|
||||
*/
|
||||
public static function from_wp_query( WP_Query $query ) {
|
||||
// A singular post (possibly the static front page).
|
||||
if ( $query->is_singular() ) {
|
||||
$post = $query->get_queried_object();
|
||||
if ( $post instanceof WP_Post && self::is_post_public( $post ) ) {
|
||||
return self::create_entity_for_post( $post, self::get_query_pagenum( $query, 'page' ) );
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
$page = self::get_query_pagenum( $query );
|
||||
|
||||
// The blog.
|
||||
if ( $query->is_home() ) {
|
||||
// The blog is either the front page...
|
||||
if ( $query->is_front_page() ) {
|
||||
return self::create_entity_for_front_blog( $page );
|
||||
}
|
||||
// ...or it is a separate post assigned as 'page_for_posts'.
|
||||
return self::create_entity_for_posts_blog( $page );
|
||||
}
|
||||
|
||||
// A taxonomy term archive.
|
||||
if ( $query->is_category() || $query->is_tag() || $query->is_tax() ) {
|
||||
$term = $query->get_queried_object();
|
||||
if ( $term instanceof WP_Term ) {
|
||||
return self::create_entity_for_term( $term, $page );
|
||||
}
|
||||
}
|
||||
|
||||
// An author archive.
|
||||
if ( $query->is_author() ) {
|
||||
$user = $query->get_queried_object();
|
||||
if ( $user instanceof WP_User ) {
|
||||
return self::create_entity_for_author( $user, $page );
|
||||
}
|
||||
}
|
||||
|
||||
// A post type archive.
|
||||
if ( $query->is_post_type_archive() ) {
|
||||
$post_type = $query->get( 'post_type' );
|
||||
if ( is_array( $post_type ) ) {
|
||||
$post_type = reset( $post_type );
|
||||
}
|
||||
$post_type_object = get_post_type_object( $post_type );
|
||||
if ( $post_type_object instanceof WP_Post_Type ) {
|
||||
return self::create_entity_for_post_type( $post_type_object, $page );
|
||||
}
|
||||
}
|
||||
|
||||
// A date-based archive.
|
||||
if ( $query->is_date() ) {
|
||||
$queried_post = self::get_first_query_post( $query );
|
||||
if ( ! $queried_post ) {
|
||||
return null;
|
||||
}
|
||||
if ( $query->is_year() ) {
|
||||
return self::create_entity_for_date( $queried_post, 'year', $page );
|
||||
}
|
||||
if ( $query->is_month() ) {
|
||||
return self::create_entity_for_date( $queried_post, 'month', $page );
|
||||
}
|
||||
if ( $query->is_day() ) {
|
||||
return self::create_entity_for_date( $queried_post, 'day', $page );
|
||||
}
|
||||
|
||||
// Time archives are not covered for now. While they can theoretically be used in WordPress, they
|
||||
// aren't fully supported, and WordPress does not link to them anywhere.
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the entity for a given post object.
|
||||
*
|
||||
* @since 1.15.0
|
||||
* @since 1.68.0 Method access modifier changed to public.
|
||||
*
|
||||
* @param WP_Post $post A WordPress post object.
|
||||
* @param int $page Page number.
|
||||
* @return Entity The entity for the post.
|
||||
*/
|
||||
public static function create_entity_for_post( WP_Post $post, $page ) {
|
||||
return new Entity(
|
||||
self::paginate_post_url( get_permalink( $post ), $post, $page ),
|
||||
array(
|
||||
'type' => 'post',
|
||||
'title' => $post->post_title,
|
||||
'id' => $post->ID,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the entity for the posts page blog archive.
|
||||
*
|
||||
* This method should only be used when the blog is handled via a separate page, i.e. when 'show_on_front' is set
|
||||
* to 'page' and the 'page_for_posts' option is set. In this case the blog is technically a post itself, therefore
|
||||
* its entity also includes an ID.
|
||||
*
|
||||
* @since 1.15.0
|
||||
*
|
||||
* @param int $page Page number.
|
||||
* @return Entity|null The entity for the posts blog archive, or null if not set.
|
||||
*/
|
||||
private static function create_entity_for_posts_blog( $page ) {
|
||||
$post_id = (int) get_option( 'page_for_posts' );
|
||||
if ( ! $post_id ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$post = get_post( $post_id );
|
||||
if ( ! $post ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Entity(
|
||||
self::paginate_entity_url( get_permalink( $post ), $page ),
|
||||
array(
|
||||
'type' => 'blog',
|
||||
'title' => $post->post_title,
|
||||
'id' => $post->ID,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the entity for the front page blog archive.
|
||||
*
|
||||
* This method should only be used when the front page is set to display the
|
||||
* blog archive, i.e. is not technically a post itself.
|
||||
*
|
||||
* @since 1.15.0
|
||||
*
|
||||
* @param int $page Page number.
|
||||
* @return Entity The entity for the front blog archive.
|
||||
*/
|
||||
private static function create_entity_for_front_blog( $page ) {
|
||||
// The translation string intentionally omits the 'google-site-kit' text domain since it should use
|
||||
// WordPress core translations.
|
||||
return new Entity(
|
||||
self::paginate_entity_url( user_trailingslashit( home_url() ), $page ),
|
||||
array(
|
||||
'type' => 'blog',
|
||||
'title' => __( 'Home', 'default' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the entity for a given term object, i.e. for a taxonomy term archive.
|
||||
*
|
||||
* @since 1.15.0
|
||||
*
|
||||
* @param WP_Term $term A WordPress term object.
|
||||
* @param int $page Page number.
|
||||
* @return Entity The entity for the term.
|
||||
*/
|
||||
private static function create_entity_for_term( WP_Term $term, $page ) {
|
||||
// See WordPress's `get_the_archive_title()` function for this behavior. The strings here intentionally omit
|
||||
// the 'google-site-kit' text domain since they should use WordPress core translations.
|
||||
switch ( $term->taxonomy ) {
|
||||
case 'category':
|
||||
$title = $term->name;
|
||||
$prefix = _x( 'Category:', 'category archive title prefix', 'default' );
|
||||
break;
|
||||
case 'post_tag':
|
||||
$title = $term->name;
|
||||
$prefix = _x( 'Tag:', 'tag archive title prefix', 'default' );
|
||||
break;
|
||||
case 'post_format':
|
||||
$prefix = '';
|
||||
switch ( $term->slug ) {
|
||||
case 'post-format-aside':
|
||||
$title = _x( 'Asides', 'post format archive title', 'default' );
|
||||
break;
|
||||
case 'post-format-gallery':
|
||||
$title = _x( 'Galleries', 'post format archive title', 'default' );
|
||||
break;
|
||||
case 'post-format-image':
|
||||
$title = _x( 'Images', 'post format archive title', 'default' );
|
||||
break;
|
||||
case 'post-format-video':
|
||||
$title = _x( 'Videos', 'post format archive title', 'default' );
|
||||
break;
|
||||
case 'post-format-quote':
|
||||
$title = _x( 'Quotes', 'post format archive title', 'default' );
|
||||
break;
|
||||
case 'post-format-link':
|
||||
$title = _x( 'Links', 'post format archive title', 'default' );
|
||||
break;
|
||||
case 'post-format-status':
|
||||
$title = _x( 'Statuses', 'post format archive title', 'default' );
|
||||
break;
|
||||
case 'post-format-audio':
|
||||
$title = _x( 'Audio', 'post format archive title', 'default' );
|
||||
break;
|
||||
case 'post-format-chat':
|
||||
$title = _x( 'Chats', 'post format archive title', 'default' );
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
$tax = get_taxonomy( $term->taxonomy );
|
||||
$title = $term->name;
|
||||
$prefix = sprintf(
|
||||
/* translators: %s: Taxonomy singular name. */
|
||||
_x( '%s:', 'taxonomy term archive title prefix', 'default' ),
|
||||
$tax->labels->singular_name
|
||||
);
|
||||
}
|
||||
|
||||
return new Entity(
|
||||
self::paginate_entity_url( get_term_link( $term ), $page ),
|
||||
array(
|
||||
'type' => 'term',
|
||||
'title' => self::prefix_title( $title, $prefix ),
|
||||
'id' => $term->term_id,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the entity for a given user object, i.e. for an author archive.
|
||||
*
|
||||
* @since 1.15.0
|
||||
*
|
||||
* @param WP_User $user A WordPress user object.
|
||||
* @param int $page Page number.
|
||||
* @return Entity The entity for the user.
|
||||
*/
|
||||
private static function create_entity_for_author( WP_User $user, $page ) {
|
||||
// See WordPress's `get_the_archive_title()` function for this behavior. The string here intentionally omits
|
||||
// the 'google-site-kit' text domain since it should use WordPress core translations.
|
||||
$title = $user->display_name;
|
||||
$prefix = _x( 'Author:', 'author archive title prefix', 'default' );
|
||||
|
||||
return new Entity(
|
||||
self::paginate_entity_url( get_author_posts_url( $user->ID, $user->user_nicename ), $page ),
|
||||
array(
|
||||
'type' => 'user',
|
||||
'title' => self::prefix_title( $title, $prefix ),
|
||||
'id' => $user->ID,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the entity for a given post type object.
|
||||
*
|
||||
* @since 1.15.0
|
||||
*
|
||||
* @param WP_Post_Type $post_type A WordPress post type object.
|
||||
* @param int $page Page number.
|
||||
* @return Entity The entity for the post type.
|
||||
*/
|
||||
private static function create_entity_for_post_type( WP_Post_Type $post_type, $page ) {
|
||||
// See WordPress's `get_the_archive_title()` function for this behavior. The string here intentionally omits
|
||||
// the 'google-site-kit' text domain since it should use WordPress core translations.
|
||||
$title = $post_type->labels->name;
|
||||
$prefix = _x( 'Archives:', 'post type archive title prefix', 'default' );
|
||||
|
||||
return new Entity(
|
||||
self::paginate_entity_url( get_post_type_archive_link( $post_type->name ), $page ),
|
||||
array(
|
||||
'type' => 'post_type',
|
||||
'title' => self::prefix_title( $title, $prefix ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the entity for a date-based archive.
|
||||
*
|
||||
* The post specified has to any post from the query, in order to extract the relevant date information.
|
||||
*
|
||||
* @since 1.15.0
|
||||
*
|
||||
* @param WP_Post $queried_post A WordPress post object from the query.
|
||||
* @param string $type Type of the date-based archive. Either 'year', 'month', or 'day'.
|
||||
* @param int $page Page number.
|
||||
* @return Entity|null The entity for the date archive, or null if unable to parse date.
|
||||
*/
|
||||
private static function create_entity_for_date( WP_Post $queried_post, $type, $page ) {
|
||||
// See WordPress's `get_the_archive_title()` function for this behavior. The strings here intentionally omit
|
||||
// the 'google-site-kit' text domain since they should use WordPress core translations.
|
||||
switch ( $type ) {
|
||||
case 'year':
|
||||
$prefix = _x( 'Year:', 'date archive title prefix', 'default' );
|
||||
$format = _x( 'Y', 'yearly archives date format', 'default' );
|
||||
$url_func = 'get_year_link';
|
||||
$url_func_format = 'Y';
|
||||
break;
|
||||
case 'month':
|
||||
$prefix = _x( 'Month:', 'date archive title prefix', 'default' );
|
||||
$format = _x( 'F Y', 'monthly archives date format', 'default' );
|
||||
$url_func = 'get_month_link';
|
||||
$url_func_format = 'Y/m';
|
||||
break;
|
||||
default:
|
||||
$type = 'day';
|
||||
$prefix = _x( 'Day:', 'date archive title prefix', 'default' );
|
||||
$format = _x( 'F j, Y', 'daily archives date format', 'default' );
|
||||
$url_func = 'get_day_link';
|
||||
$url_func_format = 'Y/m/j';
|
||||
}
|
||||
|
||||
$title = get_post_time( $format, false, $queried_post, true );
|
||||
|
||||
$url_func_args = get_post_time( $url_func_format, false, $queried_post );
|
||||
if ( ! $url_func_args ) {
|
||||
return null; // Unable to parse date, likely there is none set.
|
||||
}
|
||||
$url_func_args = array_map( 'absint', explode( '/', $url_func_args ) );
|
||||
|
||||
return new Entity(
|
||||
self::paginate_entity_url( call_user_func_array( $url_func, $url_func_args ), $page ),
|
||||
array(
|
||||
'type' => $type,
|
||||
'title' => self::prefix_title( $title, $prefix ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a given post is public, i.e. has a public URL.
|
||||
*
|
||||
* @since 1.15.0
|
||||
*
|
||||
* @param WP_Post $post A WordPress post object.
|
||||
* @return bool True if the post is public, false otherwise.
|
||||
*/
|
||||
private static function is_post_public( WP_Post $post ) {
|
||||
// If post status isn't 'publish', the post is not public.
|
||||
if ( 'publish' !== get_post_status( $post ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the post type overall is not publicly viewable, the post is not public.
|
||||
if ( ! is_post_type_viewable( $post->post_type ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Otherwise, the post is public.
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the first post from a WordPress query.
|
||||
*
|
||||
* @since 1.15.0
|
||||
*
|
||||
* @param WP_Query $query WordPress query object. Must already have run the actual database query.
|
||||
* @return WP_Post|null WordPress post object, or null if none found.
|
||||
*/
|
||||
private static function get_first_query_post( WP_Query $query ) {
|
||||
if ( ! $query->posts ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$post = reset( $query->posts );
|
||||
if ( $post instanceof WP_Post ) {
|
||||
return $post;
|
||||
}
|
||||
|
||||
if ( is_numeric( $post ) ) {
|
||||
return get_post( $post );
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Combines an entity title and prefix.
|
||||
*
|
||||
* This is based on the WordPress core function `get_the_archive_title()`.
|
||||
*
|
||||
* @since 1.15.0
|
||||
*
|
||||
* @param string $title The title.
|
||||
* @param string $prefix The prefix to add, should end in a colon.
|
||||
* @return string Resulting entity title.
|
||||
*/
|
||||
private static function prefix_title( $title, $prefix ) {
|
||||
if ( empty( $prefix ) ) {
|
||||
return $title;
|
||||
}
|
||||
|
||||
// See WordPress's `get_the_archive_title()` function for this behavior. The string here intentionally omits
|
||||
// the 'google-site-kit' text domain since it should use WordPress core translations.
|
||||
return sprintf(
|
||||
/* translators: 1: Title prefix. 2: Title. */
|
||||
_x( '%1$s %2$s', 'archive title', 'default' ),
|
||||
$prefix,
|
||||
$title
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts given entity to AMP entity if the given URL is an AMP URL.
|
||||
*
|
||||
* @since 1.42.0
|
||||
*
|
||||
* @param string $url URL to determine the entity from.
|
||||
* @param Entity $entity The initial entity.
|
||||
* @return Entity The initial or new entity for the given URL.
|
||||
*/
|
||||
private static function maybe_convert_to_amp_entity( $url, $entity ) {
|
||||
if ( is_null( $entity ) || ! defined( 'AMP__VERSION' ) ) {
|
||||
return $entity;
|
||||
}
|
||||
|
||||
$url_parts = URL::parse( $url );
|
||||
$current_url = $entity->get_url();
|
||||
|
||||
if ( ! empty( $url_parts['query'] ) ) {
|
||||
$url_query_params = array();
|
||||
|
||||
wp_parse_str( $url_parts['query'], $url_query_params );
|
||||
|
||||
// check if the $url has amp query param.
|
||||
if ( array_key_exists( 'amp', $url_query_params ) ) {
|
||||
$new_url = add_query_arg( 'amp', '1', $current_url );
|
||||
return self::convert_to_amp_entity( $new_url, $entity );
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty( $url_parts['path'] ) ) {
|
||||
// We need to correctly add trailing slash if the original url had trailing slash.
|
||||
// That's the reason why we need to check for both version.
|
||||
if ( '/amp' === substr( $url_parts['path'], -4 ) ) { // -strlen('/amp') is -4
|
||||
$new_url = untrailingslashit( $current_url ) . '/amp';
|
||||
return self::convert_to_amp_entity( $new_url, $entity );
|
||||
}
|
||||
|
||||
if ( '/amp/' === substr( $url_parts['path'], -5 ) ) { // -strlen('/amp/') is -5
|
||||
$new_url = untrailingslashit( $current_url ) . '/amp/';
|
||||
return self::convert_to_amp_entity( $new_url, $entity );
|
||||
}
|
||||
}
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts given entity to AMP entity by changing the entity URL and adding correct mode.
|
||||
*
|
||||
* @since 1.42.0
|
||||
*
|
||||
* @param string $new_url URL of the new entity.
|
||||
* @param Entity $entity The initial entity.
|
||||
* @return Entity The new entity.
|
||||
*/
|
||||
private static function convert_to_amp_entity( $new_url, $entity ) {
|
||||
$new_entity = new Entity(
|
||||
$new_url,
|
||||
array(
|
||||
'id' => $entity->get_id(),
|
||||
'type' => $entity->get_type(),
|
||||
'title' => $entity->get_title(),
|
||||
'mode' => 'amp_secondary',
|
||||
)
|
||||
);
|
||||
|
||||
return $new_entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the page number for a query, via the specified query var. Defaults to 1.
|
||||
*
|
||||
* @since 1.68.0
|
||||
*
|
||||
* @param WP_Query $query A WordPress query object.
|
||||
* @param string $query_var Optional. Query var to look for, expects 'paged' or 'page'. Default 'paged'.
|
||||
* @return int The page number.
|
||||
*/
|
||||
private static function get_query_pagenum( $query, $query_var = 'paged' ) {
|
||||
return $query->get( $query_var ) ? (int) $query->get( $query_var ) : 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Paginates an entity URL.
|
||||
*
|
||||
* Logic extracted from `paginate_links` in WordPress core.
|
||||
* https://github.com/WordPress/WordPress/blob/7f5d7f1b56087c3eb718da4bd81deb06e077bbbb/wp-includes/general-template.php#L4203
|
||||
*
|
||||
* @since 1.68.0
|
||||
*
|
||||
* @param string $url The URL to paginate.
|
||||
* @param int $pagenum The page number to add to the URL.
|
||||
* @return string The paginated URL.
|
||||
*/
|
||||
private static function paginate_entity_url( $url, $pagenum ) {
|
||||
global $wp_rewrite;
|
||||
|
||||
if ( 1 === $pagenum ) {
|
||||
return $url;
|
||||
}
|
||||
|
||||
// Setting up default values based on the given URL.
|
||||
$url_parts = explode( '?', $url );
|
||||
|
||||
// Append the format placeholder to the base URL.
|
||||
$base = trailingslashit( $url_parts[0] ) . '%_%';
|
||||
|
||||
// URL base depends on permalink settings.
|
||||
$format = $wp_rewrite->using_index_permalinks() && ! strpos( $base, 'index.php' ) ? 'index.php/' : '';
|
||||
$format .= $wp_rewrite->using_permalinks() ? user_trailingslashit( $wp_rewrite->pagination_base . '/%#%', 'paged' ) : '?paged=%#%';
|
||||
|
||||
// Array of query args to add.
|
||||
$add_args = array();
|
||||
|
||||
// Merge additional query vars found in the original URL into 'add_args' array.
|
||||
if ( isset( $url_parts[1] ) ) {
|
||||
// Find the format argument.
|
||||
$format_parts = explode( '?', str_replace( '%_%', $format, $base ) );
|
||||
$format_query = isset( $format_parts[1] ) ? $format_parts[1] : '';
|
||||
wp_parse_str( $format_query, $format_args );
|
||||
|
||||
// Find the query args of the requested URL.
|
||||
$url_query_args = array();
|
||||
wp_parse_str( $url_parts[1], $url_query_args );
|
||||
|
||||
// Remove the format argument from the array of query arguments, to avoid overwriting custom format.
|
||||
foreach ( $format_args as $format_arg => $format_arg_value ) {
|
||||
unset( $url_query_args[ $format_arg ] );
|
||||
}
|
||||
|
||||
$add_args = array_merge( $add_args, urlencode_deep( $url_query_args ) );
|
||||
}
|
||||
|
||||
$link = str_replace( '%_%', $format, $base );
|
||||
$link = str_replace( '%#%', $pagenum, $link );
|
||||
if ( $add_args ) {
|
||||
$link = add_query_arg( $add_args, $link );
|
||||
}
|
||||
|
||||
return $link;
|
||||
}
|
||||
|
||||
/**
|
||||
* Paginates a post URL.
|
||||
*
|
||||
* Logic extracted from `_wp_link_page` in WordPress core.
|
||||
* https://github.com/WordPress/WordPress/blob/7f5d7f1b56087c3eb718da4bd81deb06e077bbbb/wp-includes/post-template.php#L1031
|
||||
*
|
||||
* @since 1.68.0
|
||||
*
|
||||
* @param string $url The URL to paginate.
|
||||
* @param WP_Post $post The WordPress post object.
|
||||
* @param int $pagenum The page number to add to the URL.
|
||||
* @return string The paginated URL.
|
||||
*/
|
||||
private static function paginate_post_url( $url, $post, $pagenum ) {
|
||||
global $wp_rewrite;
|
||||
|
||||
if ( 1 === $pagenum ) {
|
||||
return $url;
|
||||
}
|
||||
|
||||
if ( ! get_option( 'permalink_structure' ) || in_array( $post->post_status, array( 'draft', 'pending' ), true ) ) {
|
||||
$url = add_query_arg( 'page', $pagenum, $url );
|
||||
} elseif ( 'page' === get_option( 'show_on_front' ) && (int) get_option( 'page_on_front' ) === (int) $post->ID ) {
|
||||
$url = trailingslashit( $url ) . user_trailingslashit( "$wp_rewrite->pagination_base/" . $pagenum, 'single_paged' );
|
||||
} else {
|
||||
$url = trailingslashit( $url ) . user_trailingslashit( $pagenum, 'single_paged' );
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
/**
|
||||
* Exit_Handler
|
||||
*
|
||||
* @package Google\Site_Kit\Core\Util
|
||||
* @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\Core\Util;
|
||||
|
||||
/**
|
||||
* Exit_Handler class.
|
||||
*
|
||||
* @since 1.1.0
|
||||
* @access private
|
||||
* @ignore
|
||||
*/
|
||||
class Exit_Handler {
|
||||
/**
|
||||
* Invokes the handler.
|
||||
*
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public function invoke() {
|
||||
$callback = static function () {
|
||||
exit;
|
||||
};
|
||||
|
||||
if ( defined( 'GOOGLESITEKIT_TESTS' ) ) {
|
||||
/**
|
||||
* Allows the callback to be filtered during tests.
|
||||
*
|
||||
* @since 1.1.0
|
||||
* @param \Closure $callback Exit handler callback.
|
||||
*/
|
||||
$callback = apply_filters( 'googlesitekit_exit_handler', $callback );
|
||||
}
|
||||
|
||||
if ( $callback instanceof \Closure ) {
|
||||
$callback();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Google\Site_Kit\Core\Util\Feature_Flags
|
||||
*
|
||||
* @package Google\Site_Kit\Core\Util
|
||||
* @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\Core\Util;
|
||||
|
||||
use ArrayAccess;
|
||||
|
||||
/**
|
||||
* Class for interacting with feature flag configuration.
|
||||
*
|
||||
* @since 1.22.0
|
||||
* @access private
|
||||
* @ignore
|
||||
*/
|
||||
class Feature_Flags {
|
||||
|
||||
/**
|
||||
* Feature flag definitions.
|
||||
*
|
||||
* @since 1.22.0
|
||||
* @var array|ArrayAccess
|
||||
*/
|
||||
private static $features = array();
|
||||
|
||||
/**
|
||||
* Checks if the given feature is enabled.
|
||||
*
|
||||
* @since 1.22.0
|
||||
*
|
||||
* @param string $feature Feature key path to check.
|
||||
* @return bool
|
||||
*/
|
||||
public static function enabled( $feature ) {
|
||||
if ( ! $feature || ! is_string( $feature ) || empty( static::$features ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters a feature flag's status (on or off).
|
||||
*
|
||||
* Mainly this is used by E2E tests to allow certain features to be disabled or
|
||||
* enabled for testing, but is also useful to switch features on/off on-the-fly.
|
||||
*
|
||||
* @since 1.25.0
|
||||
*
|
||||
* @param bool $feature_enabled The current status of this feature flag (`true` or `false`).
|
||||
* @param string $feature The feature name.
|
||||
*/
|
||||
return apply_filters( 'googlesitekit_is_feature_enabled', false, $feature );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all enabled feature flags.
|
||||
*
|
||||
* @since 1.25.0
|
||||
*
|
||||
* @return string[] An array of all enabled features.
|
||||
*/
|
||||
public static function get_enabled_features() {
|
||||
$enabled_features = array();
|
||||
|
||||
foreach ( static::$features as $feature_name ) {
|
||||
if ( static::enabled( $feature_name ) ) {
|
||||
$enabled_features[] = $feature_name;
|
||||
}
|
||||
}
|
||||
|
||||
return $enabled_features;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the feature configuration.
|
||||
*
|
||||
* @since 1.22.0
|
||||
*
|
||||
* @param array|ArrayAccess $features Feature configuration.
|
||||
*/
|
||||
public static function set_features( $features ) {
|
||||
if ( is_array( $features ) || $features instanceof ArrayAccess ) {
|
||||
static::$features = $features;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all available feature flags.
|
||||
*
|
||||
* @since 1.26.0
|
||||
*
|
||||
* @return array An array of all available features.
|
||||
*/
|
||||
public static function get_available_features() {
|
||||
return static::$features;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Google\Site_Kit\Core\Util\Google_Icon
|
||||
*
|
||||
* @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\Core\Util;
|
||||
|
||||
/**
|
||||
* Class for the Google SVG Icon
|
||||
*
|
||||
* @since 1.28.0
|
||||
* @access private
|
||||
* @ignore
|
||||
*/
|
||||
final class Google_Icon {
|
||||
|
||||
/**
|
||||
* We use fill="white" as a placeholder attribute that we replace in with_fill()
|
||||
* to match the colorscheme that the user has set.
|
||||
*
|
||||
* See the comment in includes/Core/Admin/Screen.php::add() for more information.
|
||||
*/
|
||||
const XML = '<svg width="20" height="20" viewbox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill="white" d="M17.6 8.5h-7.5v3h4.4c-.4 2.1-2.3 3.5-4.4 3.4-2.6-.1-4.6-2.1-4.7-4.7-.1-2.7 2-5 4.7-5.1 1.1 0 2.2.4 3.1 1.2l2.3-2.2C14.1 2.7 12.1 2 10.2 2c-4.4 0-8 3.6-8 8s3.6 8 8 8c4.6 0 7.7-3.2 7.7-7.8-.1-.6-.1-1.1-.3-1.7z" fillrule="evenodd" cliprule="evenodd"></path></svg>';
|
||||
|
||||
/**
|
||||
* Returns a base64 encoded version of the SVG.
|
||||
*
|
||||
* @since 1.28.0
|
||||
*
|
||||
* @param string $source SVG icon source.
|
||||
* @return string Base64 representation of SVG
|
||||
*/
|
||||
public static function to_base64( $source = self::XML ) {
|
||||
return base64_encode( $source );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns SVG XML with fill color replaced.
|
||||
*
|
||||
* @since 1.28.0
|
||||
*
|
||||
* @param string $color Any valid color for css, either word or hex code.
|
||||
* @return string SVG XML with the fill color replaced
|
||||
*/
|
||||
public static function with_fill( $color ) {
|
||||
return str_replace( 'white', esc_attr( $color ), self::XML );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
/**
|
||||
* Trait Google\Site_Kit\Core\Util\Google_URL_Matcher_Trait
|
||||
*
|
||||
* @package Google\Site_Kit\Core\Util
|
||||
* @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\Core\Util;
|
||||
|
||||
/**
|
||||
* Trait for matching URLs and domains for Google Site Verification and Search Console.
|
||||
*
|
||||
* @since 1.6.0
|
||||
* @access private
|
||||
* @ignore
|
||||
*/
|
||||
trait Google_URL_Matcher_Trait {
|
||||
|
||||
/**
|
||||
* Compares two URLs for whether they qualify for a Site Verification or Search Console URL match.
|
||||
*
|
||||
* In order for the URLs to be considered a match, they have to be fully equal, except for a potential
|
||||
* trailing slash in one of them, which will be ignored.
|
||||
*
|
||||
* @since 1.6.0
|
||||
*
|
||||
* @param string $url The URL.
|
||||
* @param string $compare The URL to compare.
|
||||
* @return bool True if the URLs are considered a match, false otherwise.
|
||||
*/
|
||||
protected function is_url_match( $url, $compare ) {
|
||||
$url = untrailingslashit( $url );
|
||||
$compare = untrailingslashit( $compare );
|
||||
$url_normalizer = new Google_URL_Normalizer();
|
||||
$url = $url_normalizer->normalize_url( $url );
|
||||
$compare = $url_normalizer->normalize_url( $compare );
|
||||
|
||||
return $url === $compare;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares two domains for whether they qualify for a Site Verification or Search Console domain match.
|
||||
*
|
||||
* The value to compare may be either a domain or a full URL. If the latter, its scheme and a potential trailing
|
||||
* slash will be stripped out before the comparison.
|
||||
*
|
||||
* In order for the comparison to be considered a match then, the domains have to fully match, except for a
|
||||
* potential "www." prefix, which will be ignored. If the value to compare is a full URL and includes a path other
|
||||
* than just a trailing slash, it will not be a match.
|
||||
*
|
||||
* @since 1.6.0
|
||||
*
|
||||
* @param string $domain A domain.
|
||||
* @param string $compare The domain or URL to compare.
|
||||
* @return bool True if the URLs/domains are considered a match, false otherwise.
|
||||
*/
|
||||
protected function is_domain_match( $domain, $compare ) {
|
||||
$domain = $this->strip_domain_www( $domain );
|
||||
$compare = $this->strip_domain_www( $this->strip_url_scheme( untrailingslashit( $compare ) ) );
|
||||
$url_normalizer = new Google_URL_Normalizer();
|
||||
$domain = $url_normalizer->normalize_url( $domain );
|
||||
$compare = $url_normalizer->normalize_url( $compare );
|
||||
|
||||
return $domain === $compare;
|
||||
}
|
||||
|
||||
/**
|
||||
* Strips the scheme from a URL.
|
||||
*
|
||||
* @since 1.6.0
|
||||
*
|
||||
* @param string $url URL with or without scheme.
|
||||
* @return string The passed $url without its scheme.
|
||||
*/
|
||||
protected function strip_url_scheme( $url ) {
|
||||
return preg_replace( '#^(\w+:)?//#', '', $url );
|
||||
}
|
||||
|
||||
/**
|
||||
* Strips the "www." prefix from a domain.
|
||||
*
|
||||
* @since 1.6.0
|
||||
*
|
||||
* @param string $domain Domain with or without "www." prefix.
|
||||
* @return string The passed $domain without "www." prefix.
|
||||
*/
|
||||
protected function strip_domain_www( $domain ) {
|
||||
return preg_replace( '/^www\./', '', $domain );
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Google\Site_Kit\Core\Util\Google_URL_Normalizer
|
||||
*
|
||||
* @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\Core\Util;
|
||||
|
||||
/**
|
||||
* Class handling URL normalization for comparisons and API requests.
|
||||
*
|
||||
* @since 1.18.0
|
||||
* @access private
|
||||
* @ignore
|
||||
*/
|
||||
final class Google_URL_Normalizer {
|
||||
|
||||
/**
|
||||
* Normalizes a URL by converting to all lowercase, converting Unicode characters
|
||||
* to punycode, and removing bidirectional control characters.
|
||||
*
|
||||
* @since 1.18.0
|
||||
*
|
||||
* @param string $url The URL or domain to normalize.
|
||||
* @return string The normalized URL or domain.
|
||||
*/
|
||||
public function normalize_url( $url ) {
|
||||
// Remove bidirectional control characters.
|
||||
$url = preg_replace( array( '/\xe2\x80\xac/', '/\xe2\x80\xab/' ), '', $url );
|
||||
$url = $this->decode_unicode_url_or_domain( $url );
|
||||
$url = strtolower( $url );
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Punycode version of a Unicode URL or domain name.
|
||||
*
|
||||
* @since 1.18.0
|
||||
*
|
||||
* @param string $url The URL or domain name to decode.
|
||||
*/
|
||||
protected function decode_unicode_url_or_domain( $url ) {
|
||||
$encoder_class = class_exists( '\WpOrg\Requests\IdnaEncoder' ) ? '\WpOrg\Requests\IdnaEncoder' : '\Requests_IDNAEncoder';
|
||||
|
||||
$parts = URL::parse( $url );
|
||||
if ( ! $parts || ! isset( $parts['host'] ) || '' === $parts['host'] ) {
|
||||
return $encoder_class::encode( $url );
|
||||
}
|
||||
$decoded_host = $encoder_class::encode( $parts['host'] );
|
||||
return str_replace( $parts['host'], $decoded_host, $url );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Google\Site_Kit\Core\Util\Health_Checks
|
||||
*
|
||||
* @package Google\Site_Kit\Core\Util
|
||||
* @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\Core\Util;
|
||||
|
||||
use Exception;
|
||||
use Google\Site_Kit\Core\Authentication\Authentication;
|
||||
use Google\Site_Kit\Core\Permissions\Permissions;
|
||||
use Google\Site_Kit\Core\REST_API\REST_Route;
|
||||
use Google\Site_Kit_Dependencies\Google\Service\SearchConsole as Google_Service_SearchConsole;
|
||||
use Google\Site_Kit_Dependencies\Google_Service_Exception;
|
||||
use WP_REST_Server;
|
||||
|
||||
/**
|
||||
* Class for performing health checks.
|
||||
*
|
||||
* @since 1.14.0
|
||||
* @access private
|
||||
* @ignore
|
||||
*/
|
||||
class Health_Checks {
|
||||
|
||||
/**
|
||||
* Authentication instance.
|
||||
*
|
||||
* @var Authentication
|
||||
*/
|
||||
protected $authentication;
|
||||
|
||||
/**
|
||||
* Google_Proxy instance.
|
||||
*
|
||||
* @var Google_Proxy
|
||||
*/
|
||||
protected $google_proxy;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param Authentication $authentication Authentication instance.
|
||||
*/
|
||||
public function __construct( Authentication $authentication ) {
|
||||
$this->authentication = $authentication;
|
||||
$this->google_proxy = $authentication->get_google_proxy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers functionality through WordPress hooks.
|
||||
*
|
||||
* @since 1.14.0
|
||||
*/
|
||||
public function register() {
|
||||
add_filter(
|
||||
'googlesitekit_rest_routes',
|
||||
function ( $rest_routes ) {
|
||||
$health_check_routes = $this->get_rest_routes();
|
||||
|
||||
return array_merge( $rest_routes, $health_check_routes );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all health check REST routes.
|
||||
*
|
||||
* @since 1.14.0
|
||||
*
|
||||
* @return REST_Route[] List of REST_Route objects.
|
||||
*/
|
||||
private function get_rest_routes() {
|
||||
return array(
|
||||
new REST_Route(
|
||||
'core/site/data/health-checks',
|
||||
array(
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => function() {
|
||||
$checks = array(
|
||||
'googleAPI' => $this->check_google_api(),
|
||||
'skService' => $this->check_service_connectivity(),
|
||||
);
|
||||
|
||||
return compact( 'checks' );
|
||||
},
|
||||
'permission_callback' => function () {
|
||||
return current_user_can( Permissions::VIEW_SHARED_DASHBOARD ) || current_user_can( Permissions::SETUP );
|
||||
},
|
||||
),
|
||||
)
|
||||
),
|
||||
new REST_Route(
|
||||
'core/site/data/connection-check',
|
||||
array(
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => function() {
|
||||
return 'true';
|
||||
},
|
||||
'permission_callback' => function () {
|
||||
return current_user_can( Permissions::VIEW_SHARED_DASHBOARD ) || current_user_can( Permissions::SETUP );
|
||||
},
|
||||
),
|
||||
)
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks connection to Google APIs.
|
||||
*
|
||||
* @since 1.14.0
|
||||
*
|
||||
* @return array Results data.
|
||||
*/
|
||||
private function check_google_api() {
|
||||
$client = $this->authentication->get_oauth_client()->get_client();
|
||||
$restore_defer = $client->withDefer( false );
|
||||
$error_msg = '';
|
||||
|
||||
// Make a request to the Search API.
|
||||
// This request is bound to fail but this is okay as long as the error response comes
|
||||
// from a Google API endpoint (Google_Service_exception). The test is only intended
|
||||
// to check that the server is capable of connecting to the Google API (at all)
|
||||
// regardless of valid authentication, which will likely be missing here.
|
||||
try {
|
||||
( new Google_Service_SearchConsole( $client ) )->sites->listSites();
|
||||
$pass = true;
|
||||
} catch ( Google_Service_Exception $e ) {
|
||||
if ( ! empty( $e->getErrors() ) ) {
|
||||
$pass = true;
|
||||
} else {
|
||||
$pass = false;
|
||||
$error_msg = $e->getMessage();
|
||||
}
|
||||
} catch ( Exception $e ) {
|
||||
$pass = false;
|
||||
$error_msg = $e->getMessage();
|
||||
}
|
||||
$restore_defer();
|
||||
|
||||
return array(
|
||||
'pass' => $pass,
|
||||
'errorMsg' => $error_msg,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks connection to Site Kit service.
|
||||
*
|
||||
* @since 1.85.0
|
||||
*
|
||||
* @return array Results data.
|
||||
*/
|
||||
private function check_service_connectivity() {
|
||||
$service_url = $this->google_proxy->url();
|
||||
$response = wp_remote_head( $service_url );
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
return array(
|
||||
'pass' => false,
|
||||
'errorMsg' => $response->get_error_message(),
|
||||
);
|
||||
}
|
||||
|
||||
$status_code = wp_remote_retrieve_response_code( $response );
|
||||
$pass = is_int( $status_code ) && $status_code < 400;
|
||||
|
||||
return array(
|
||||
'pass' => $pass,
|
||||
'errorMsg' => $pass ? '' : 'connection_fail',
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Google\Site_Kit\Core\Util\Input
|
||||
*
|
||||
* @package Google\Site_Kit\Core\Util
|
||||
* @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\Core\Util;
|
||||
|
||||
/**
|
||||
* Class for input superglobal access.
|
||||
*
|
||||
* @since 1.1.2
|
||||
* @access private
|
||||
* @ignore
|
||||
*/
|
||||
class Input {
|
||||
/**
|
||||
* Map of input type to superglobal array.
|
||||
*
|
||||
* For use as fallback only.
|
||||
*
|
||||
* @since 1.1.4
|
||||
* @var array
|
||||
*/
|
||||
protected $fallback_map;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since 1.1.4
|
||||
*/
|
||||
public function __construct() {
|
||||
// Fallback map for environments where filter_input may not work with ENV or SERVER types.
|
||||
$this->fallback_map = array(
|
||||
INPUT_ENV => $_ENV,
|
||||
INPUT_SERVER => $_SERVER, // phpcs:ignore WordPress.VIP.SuperGlobalInputUsage
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a specific external variable by name and optionally filters it.
|
||||
*
|
||||
* @since 1.1.2
|
||||
* @since 1.92.0 Changed default value of $options parameter to 0.
|
||||
*
|
||||
* @link https://php.net/manual/en/function.filter-input.php
|
||||
*
|
||||
* @param int $type One of INPUT_GET, INPUT_POST, INPUT_COOKIE, INPUT_SERVER, or INPUT_ENV.
|
||||
* @param string $variable_name Name of a variable to get.
|
||||
* @param int $filter [optional] The ID of the filter to apply. The manual page lists the available filters.
|
||||
* @param mixed $options [optional] Associative array of options or bitwise disjunction of flags.
|
||||
* If filter accepts options, flags can be provided in "flags" field of array.
|
||||
* @return mixed Value of the requested variable on success,
|
||||
* FALSE if the filter fails,
|
||||
* NULL if the $variable_name variable is not set.
|
||||
*
|
||||
* If the flag FILTER_NULL_ON_FAILURE is used, it returns FALSE if the variable is not set
|
||||
* and NULL if the filter fails.
|
||||
*/
|
||||
public function filter( $type, $variable_name, $filter = FILTER_DEFAULT, $options = 0 ) {
|
||||
$value = filter_input( $type, $variable_name, $filter, $options );
|
||||
|
||||
// Fallback for environments where filter_input may not work with specific types.
|
||||
if (
|
||||
// Only use this fallback for affected input types.
|
||||
isset( $this->fallback_map[ $type ] )
|
||||
// Only use the fallback if the value is not-set (could be either depending on FILTER_NULL_ON_FAILURE).
|
||||
&& in_array( $value, array( null, false ), true )
|
||||
// Only use the fallback if the key exists in the input map.
|
||||
&& array_key_exists( $variable_name, $this->fallback_map[ $type ] )
|
||||
) {
|
||||
return filter_var( $this->fallback_map[ $type ][ $variable_name ], $filter, $options );
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Google\Site_Kit\Core\Util\Method_Proxy_Trait
|
||||
*
|
||||
* @package Google\Site_Kit\Core\Util
|
||||
* @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\Core\Util;
|
||||
|
||||
trait Method_Proxy_Trait {
|
||||
|
||||
/**
|
||||
* Gets a proxy function for a class method.
|
||||
*
|
||||
* @since 1.17.0
|
||||
*
|
||||
* @param string $method Method name.
|
||||
* @return callable A proxy function.
|
||||
*/
|
||||
private function get_method_proxy( $method ) {
|
||||
return function ( ...$args ) use ( $method ) {
|
||||
return $this->{ $method }( ...$args );
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a proxy function for a class method which can be executed only once.
|
||||
*
|
||||
* @since 1.24.0
|
||||
*
|
||||
* @param string $method Method name.
|
||||
* @return callable A proxy function.
|
||||
*/
|
||||
private function get_method_proxy_once( $method ) {
|
||||
return function ( ...$args ) use ( $method ) {
|
||||
static $called;
|
||||
static $return_value;
|
||||
|
||||
if ( ! $called ) {
|
||||
$called = true;
|
||||
$return_value = $this->{ $method }( ...$args );
|
||||
}
|
||||
|
||||
return $return_value;
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
/**
|
||||
* Trait Google\Site_Kit\Core\Util\Migrate_Legacy_Keys
|
||||
*
|
||||
* @package Google\Site_Kit\Core\Util
|
||||
* @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\Core\Util;
|
||||
|
||||
/**
|
||||
* Trait for a class that migrates array keys from old to new.
|
||||
*
|
||||
* @since 1.2.0
|
||||
* @access private
|
||||
* @ignore
|
||||
*/
|
||||
trait Migrate_Legacy_Keys {
|
||||
|
||||
/**
|
||||
* Migrates legacy array keys to the current key.
|
||||
*
|
||||
* @since 1.2.0
|
||||
*
|
||||
* @param array $array Input associative array to migrate keys for.
|
||||
* @param array $key_mapping Map of legacy key to current key.
|
||||
* @return array Updated array.
|
||||
*/
|
||||
protected function migrate_legacy_keys( array $array, array $key_mapping ) {
|
||||
foreach ( $key_mapping as $legacy_key => $current_key ) {
|
||||
if ( ! isset( $array[ $current_key ] ) && isset( $array[ $legacy_key ] ) ) {
|
||||
$array[ $current_key ] = $array[ $legacy_key ];
|
||||
}
|
||||
unset( $array[ $legacy_key ] );
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
<?php
|
||||
/**
|
||||
* Migration for 1.123.0
|
||||
*
|
||||
* @package Google\Site_Kit\Core\Util
|
||||
* @copyright 2024 Google LLC
|
||||
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
|
||||
* @link https://sitekit.withgoogle.com
|
||||
*/
|
||||
|
||||
namespace Google\Site_Kit\Core\Util;
|
||||
|
||||
use Google\Site_Kit\Context;
|
||||
use Google\Site_Kit\Core\Modules\Module_Sharing_Settings;
|
||||
use Google\Site_Kit\Core\Modules\Modules;
|
||||
use Google\Site_Kit\Core\Storage\Options;
|
||||
use Google\Site_Kit\Modules\Analytics_4;
|
||||
use Google\Site_Kit\Modules\Analytics_4\Settings as Analytics_Settings;
|
||||
|
||||
/**
|
||||
* Class Migration_1_123_0
|
||||
*
|
||||
* @since 1.123.0
|
||||
* @access private
|
||||
* @ignore
|
||||
*/
|
||||
class Migration_1_123_0 {
|
||||
/**
|
||||
* Target DB version.
|
||||
*/
|
||||
const DB_VERSION = '1.123.0';
|
||||
|
||||
/**
|
||||
* DB version option name.
|
||||
*/
|
||||
const DB_VERSION_OPTION = 'googlesitekit_db_version';
|
||||
|
||||
/**
|
||||
* Legacy analytics module slug.
|
||||
*/
|
||||
const LEGACY_ANALYTICS_MODULE_SLUG = 'analytics';
|
||||
|
||||
/**
|
||||
* Legacy analytics option name.
|
||||
*/
|
||||
const LEGACY_ANALYTICS_OPTION = 'googlesitekit_analytics_settings';
|
||||
|
||||
/**
|
||||
* Context instance.
|
||||
*
|
||||
* @since 1.123.0
|
||||
* @var Context
|
||||
*/
|
||||
protected $context;
|
||||
|
||||
/**
|
||||
* Options instance.
|
||||
*
|
||||
* @since 1.123.0
|
||||
* @var Options
|
||||
*/
|
||||
protected $options;
|
||||
|
||||
/**
|
||||
* Analytics_Settings instance.
|
||||
*
|
||||
* @since 1.123.0
|
||||
* @var Analytics_Settings
|
||||
*/
|
||||
protected $analytics_settings;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since 1.123.0
|
||||
*
|
||||
* @param Context $context Plugin context instance.
|
||||
* @param Options $options Optional. Options instance.
|
||||
*/
|
||||
public function __construct(
|
||||
Context $context,
|
||||
Options $options = null
|
||||
) {
|
||||
$this->context = $context;
|
||||
$this->options = $options ?: new Options( $context );
|
||||
$this->analytics_settings = new Analytics_Settings( $this->options );
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers hooks.
|
||||
*
|
||||
* @since 1.123.0
|
||||
*/
|
||||
public function register() {
|
||||
add_action( 'admin_init', array( $this, 'migrate' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrates the DB.
|
||||
*
|
||||
* @since 1.123.0
|
||||
*/
|
||||
public function migrate() {
|
||||
$db_version = $this->options->get( self::DB_VERSION_OPTION );
|
||||
|
||||
if ( ! $db_version || version_compare( $db_version, self::DB_VERSION, '<' ) ) {
|
||||
$this->migrate_legacy_analytics_settings();
|
||||
$this->activate_analytics();
|
||||
$this->migrate_legacy_analytics_sharing_settings();
|
||||
|
||||
$this->options->set( self::DB_VERSION_OPTION, self::DB_VERSION );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrates the legacy analytics settings over to analytics-4.
|
||||
*
|
||||
* @since 1.123.0
|
||||
*/
|
||||
protected function migrate_legacy_analytics_settings() {
|
||||
if ( ! $this->analytics_settings->has() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$legacy_settings = $this->options->get( self::LEGACY_ANALYTICS_OPTION );
|
||||
|
||||
if ( empty( $legacy_settings ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$recovered_settings = array();
|
||||
$options_to_migrate = array(
|
||||
'accountID',
|
||||
'adsConversionID',
|
||||
'trackingDisabled',
|
||||
);
|
||||
|
||||
array_walk(
|
||||
$options_to_migrate,
|
||||
function( $setting ) use ( &$recovered_settings, $legacy_settings ) {
|
||||
$recovered_settings[ $setting ] = $legacy_settings[ $setting ];
|
||||
}
|
||||
);
|
||||
|
||||
if ( ! empty( $recovered_settings ) ) {
|
||||
$this->analytics_settings->merge(
|
||||
$recovered_settings
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Activates the analytics-4 module if the legacy analytics module was active.
|
||||
*
|
||||
* @since 1.123.0
|
||||
*/
|
||||
protected function activate_analytics() {
|
||||
$option = $this->options->get( Modules::OPTION_ACTIVE_MODULES );
|
||||
|
||||
// Check legacy option.
|
||||
if ( ! is_array( $option ) ) {
|
||||
$option = $this->options->get( 'googlesitekit-active-modules' );
|
||||
}
|
||||
|
||||
if ( ! is_array( $option ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$analytics_active = in_array( Analytics_4::MODULE_SLUG, $option, true );
|
||||
|
||||
// If analytics-4 is already active, bail.
|
||||
if ( $analytics_active ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$legacy_analytics_active = in_array(
|
||||
self::LEGACY_ANALYTICS_MODULE_SLUG,
|
||||
$option,
|
||||
true
|
||||
);
|
||||
|
||||
if ( $legacy_analytics_active ) {
|
||||
$option[] = Analytics_4::MODULE_SLUG;
|
||||
|
||||
$this->options->set( Modules::OPTION_ACTIVE_MODULES, $option );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replicates sharing settings from the legacy analytics module to analytics-4.
|
||||
*
|
||||
* @since 1.123.0
|
||||
*/
|
||||
protected function migrate_legacy_analytics_sharing_settings() {
|
||||
$option = $this->options->get( Module_Sharing_Settings::OPTION );
|
||||
|
||||
if ( ! is_array( $option ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If sharing settings for analytics-4 already exist, bail.
|
||||
if ( isset( $option[ Analytics_4::MODULE_SLUG ] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( isset( $option[ self::LEGACY_ANALYTICS_MODULE_SLUG ] ) ) {
|
||||
$option[ Analytics_4::MODULE_SLUG ] = $option[ self::LEGACY_ANALYTICS_MODULE_SLUG ];
|
||||
|
||||
$this->options->set( Module_Sharing_Settings::OPTION, $option );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
<?php
|
||||
/**
|
||||
* Migration for 1.3.0
|
||||
*
|
||||
* @package Google\Site_Kit\Core\Util
|
||||
* @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\Core\Util;
|
||||
|
||||
use Google\Site_Kit\Context;
|
||||
use Google\Site_Kit\Core\Authentication\Clients\OAuth_Client;
|
||||
use Google\Site_Kit\Core\Storage\Options;
|
||||
use Google\Site_Kit\Core\Storage\User_Options;
|
||||
use Google\Site_Kit\Core\Tracking\Tracking_Consent;
|
||||
|
||||
/**
|
||||
* Class Migration_1_3_0
|
||||
*
|
||||
* @since 1.3.0
|
||||
* @access private
|
||||
* @ignore
|
||||
*/
|
||||
class Migration_1_3_0 {
|
||||
/**
|
||||
* Target DB version.
|
||||
*/
|
||||
const DB_VERSION = '1.3.0';
|
||||
|
||||
/**
|
||||
* Context instance.
|
||||
*
|
||||
* @var Context
|
||||
*/
|
||||
protected $context;
|
||||
|
||||
/**
|
||||
* Options instance.
|
||||
*
|
||||
* @var Options
|
||||
*/
|
||||
protected $options;
|
||||
|
||||
/**
|
||||
* User_Options instance.
|
||||
*
|
||||
* @var User_Options
|
||||
*/
|
||||
protected $user_options;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since 1.3.0
|
||||
*
|
||||
* @param Context $context Plugin context instance.
|
||||
* @param Options $options Optional. Options instance.
|
||||
* @param User_Options $user_options Optional. User_Options instance.
|
||||
*/
|
||||
public function __construct(
|
||||
Context $context,
|
||||
Options $options = null,
|
||||
User_Options $user_options = null
|
||||
) {
|
||||
$this->context = $context;
|
||||
$this->options = $options ?: new Options( $context );
|
||||
$this->user_options = $user_options ?: new User_Options( $context );
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers hooks.
|
||||
*
|
||||
* @since 1.3.0
|
||||
*/
|
||||
public function register() {
|
||||
add_action( 'admin_init', array( $this, 'migrate' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrates the DB.
|
||||
*
|
||||
* @since 1.3.0
|
||||
*/
|
||||
public function migrate() {
|
||||
$db_version = $this->options->get( 'googlesitekit_db_version' );
|
||||
|
||||
if ( ! $db_version || version_compare( $db_version, self::DB_VERSION, '<' ) ) {
|
||||
$this->migrate_tracking_opt_in();
|
||||
|
||||
$this->options->set( 'googlesitekit_db_version', self::DB_VERSION );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrates the global tracking opt-in to a user option.
|
||||
*
|
||||
* @since 1.3.0
|
||||
* @since 1.4.0 Migrates preference for up to 20 users.
|
||||
*/
|
||||
private function migrate_tracking_opt_in() {
|
||||
// Only migrate if tracking was opted-in.
|
||||
if ( $this->options->get( Tracking_Consent::OPTION ) ) {
|
||||
$backup_user_id = $this->user_options->get_user_id();
|
||||
|
||||
foreach ( $this->get_authenticated_users() as $user_id ) {
|
||||
$this->user_options->switch_user( $user_id );
|
||||
$this->user_options->set( Tracking_Consent::OPTION, 1 );
|
||||
}
|
||||
|
||||
$this->user_options->switch_user( $backup_user_id );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the authenticated users connected to Site Kit.
|
||||
*
|
||||
* @since 1.4.0
|
||||
*
|
||||
* @return string[] User IDs of authenticated users. Maximum of 20.
|
||||
*/
|
||||
private function get_authenticated_users() {
|
||||
return get_users(
|
||||
array(
|
||||
'meta_key' => $this->user_options->get_meta_key( OAuth_Client::OPTION_ACCESS_TOKEN ), // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
|
||||
'meta_compare' => 'EXISTS',
|
||||
'number' => 20,
|
||||
'fields' => 'ID',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
<?php
|
||||
/**
|
||||
* Migration for 1.8.1
|
||||
*
|
||||
* @package Google\Site_Kit\Core\Util
|
||||
* @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\Core\Util;
|
||||
|
||||
use Google\Site_Kit\Context;
|
||||
use Google\Site_Kit\Core\Authentication\Authentication;
|
||||
use Google\Site_Kit\Core\Authentication\Google_Proxy;
|
||||
use Google\Site_Kit\Core\Authentication\Profile;
|
||||
use Google\Site_Kit\Core\Authentication\Verification_File;
|
||||
use Google\Site_Kit\Core\Authentication\Verification_Meta;
|
||||
use Google\Site_Kit\Core\Permissions\Permissions;
|
||||
use Google\Site_Kit\Core\Storage\Options;
|
||||
use Google\Site_Kit\Core\Storage\User_Options;
|
||||
use WP_User;
|
||||
use WP_Error;
|
||||
|
||||
/**
|
||||
* Class Migration_1_8_1
|
||||
*
|
||||
* @since 1.8.1
|
||||
* @access private
|
||||
* @ignore
|
||||
*/
|
||||
class Migration_1_8_1 {
|
||||
/**
|
||||
* Target DB version.
|
||||
*/
|
||||
const DB_VERSION = '1.8.1';
|
||||
|
||||
/**
|
||||
* Context instance.
|
||||
*
|
||||
* @since 1.8.1
|
||||
* @var Context
|
||||
*/
|
||||
protected $context;
|
||||
|
||||
/**
|
||||
* Options instance.
|
||||
*
|
||||
* @since 1.8.1
|
||||
* @var Options
|
||||
*/
|
||||
protected $options;
|
||||
|
||||
/**
|
||||
* User_Options instance.
|
||||
*
|
||||
* @since 1.8.1
|
||||
* @var User_Options
|
||||
*/
|
||||
protected $user_options;
|
||||
|
||||
/**
|
||||
* Authentication instance.
|
||||
*
|
||||
* @since 1.8.1
|
||||
* @var Authentication
|
||||
*/
|
||||
protected $authentication;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @param Context $context Plugin context instance.
|
||||
* @param Options $options Optional. Options instance.
|
||||
* @param User_Options $user_options Optional. User_Options instance.
|
||||
* @param Authentication $authentication Optional. Authentication instance. Default is a new instance.
|
||||
*/
|
||||
public function __construct(
|
||||
Context $context,
|
||||
Options $options = null,
|
||||
User_Options $user_options = null,
|
||||
Authentication $authentication = null
|
||||
) {
|
||||
$this->context = $context;
|
||||
$this->options = $options ?: new Options( $this->context );
|
||||
$this->user_options = $user_options ?: new User_Options( $this->context );
|
||||
$this->authentication = $authentication ?: new Authentication( $this->context, $this->options, $this->user_options );
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers hooks.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*/
|
||||
public function register() {
|
||||
add_action( 'admin_init', array( $this, 'migrate' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrates the DB.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*/
|
||||
public function migrate() {
|
||||
$db_version = $this->options->get( 'googlesitekit_db_version' );
|
||||
|
||||
// Do not run if database version already updated.
|
||||
if ( $db_version && version_compare( $db_version, self::DB_VERSION, '>=' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only run routine if using the authentication service, otherwise it
|
||||
// is irrelevant.
|
||||
if ( ! $this->authentication->credentials()->using_proxy() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only run routine once site credentials present, otherwise it is not
|
||||
// possible to connect to the authentication service.
|
||||
if ( ! $this->authentication->credentials()->has() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->clear_and_flag_unauthorized_verified_users();
|
||||
|
||||
// Update database version.
|
||||
$this->options->set( 'googlesitekit_db_version', self::DB_VERSION );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether there are any users that are verified without proper
|
||||
* authorization, clear their Site Kit data, and flag them on the
|
||||
* authentication service.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @return boolean|WP_Error True on success, WP_Error on failure.
|
||||
*/
|
||||
private function clear_and_flag_unauthorized_verified_users() {
|
||||
// Detect all unauthorized verified users and clean their Site Kit data.
|
||||
$unauthorized_identifiers = $this->clear_unauthorized_verified_users();
|
||||
|
||||
// If no unauthorized verified users found, all is well, no need to
|
||||
// show a notification.
|
||||
if ( empty( $unauthorized_identifiers ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Flag site as affected so that the notification to inform and explain
|
||||
// steps to resolve will be shown.
|
||||
$credentials = $this->authentication->credentials()->get();
|
||||
$google_proxy = new Google_Proxy( $this->context );
|
||||
$response = wp_remote_post(
|
||||
$google_proxy->url( '/notifications/mark/' ),
|
||||
array(
|
||||
'body' => array(
|
||||
'site_id' => $credentials['oauth2_client_id'],
|
||||
'site_secret' => $credentials['oauth2_client_secret'],
|
||||
'notification_id' => 'verification_leak',
|
||||
'notification_state' => 'required',
|
||||
// This is a special parameter only supported for this
|
||||
// particular notification.
|
||||
'identifiers' => implode( ',', $unauthorized_identifiers ),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$response_code = wp_remote_retrieve_response_code( $response );
|
||||
if ( 200 !== $response_code ) {
|
||||
$body = wp_remote_retrieve_body( $response );
|
||||
$decoded = json_decode( $body, true );
|
||||
return new WP_Error( $response_code, ! empty( $decoded['error'] ) ? $decoded['error'] : $body );
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for any users that are verified without proper authorization and
|
||||
* clears all their Site Kit data.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @return array List of email addresses for the unauthorized users.
|
||||
*/
|
||||
private function clear_unauthorized_verified_users() {
|
||||
global $wpdb;
|
||||
|
||||
$unauthorized_identifiers = array();
|
||||
$profile = new Profile( $this->user_options );
|
||||
|
||||
// Store original user ID to switch back later.
|
||||
$backup_user_id = $this->user_options->get_user_id();
|
||||
|
||||
// Iterate through all users verified via Site Kit.
|
||||
foreach ( $this->get_verified_user_ids() as $user_id ) {
|
||||
$this->user_options->switch_user( $user_id );
|
||||
|
||||
// If the user has setup access, there is no problem.
|
||||
if ( user_can( $user_id, Permissions::SETUP ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Try to get profile email, otherwise fall back to WP email.
|
||||
if ( $this->authentication->profile()->has() ) {
|
||||
$unauthorized_identifiers[] = $this->authentication->profile()->get()['email'];
|
||||
} else {
|
||||
$user = get_user_by( 'id', $user_id );
|
||||
$unauthorized_identifiers[] = $user->user_email;
|
||||
}
|
||||
|
||||
$prefix = $this->user_options->get_meta_key( 'googlesitekit\_%' );
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery
|
||||
$wpdb->query(
|
||||
$wpdb->prepare( "DELETE FROM $wpdb->usermeta WHERE user_id = %d AND meta_key LIKE %s", $user_id, $prefix )
|
||||
);
|
||||
wp_cache_delete( $user_id, 'user_meta' );
|
||||
}
|
||||
|
||||
// Restore original user ID.
|
||||
$this->user_options->switch_user( $backup_user_id );
|
||||
|
||||
return $unauthorized_identifiers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all user IDs that are verified via Site Kit.
|
||||
*
|
||||
* @since @1.31.0
|
||||
*
|
||||
* @return array List of user ids of verified users. Maximum of 20.
|
||||
*/
|
||||
private function get_verified_user_ids() {
|
||||
global $wpdb;
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery
|
||||
return $wpdb->get_col(
|
||||
$wpdb->prepare(
|
||||
"SELECT user_id FROM $wpdb->usermeta WHERE meta_key IN (%s, %s) LIMIT 20",
|
||||
$this->user_options->get_meta_key( Verification_File::OPTION ),
|
||||
$this->user_options->get_meta_key( Verification_Meta::OPTION )
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Google\Site_Kit\Core\Util\REST_Entity_Search_Controller
|
||||
*
|
||||
* @package Google\Site_Kit\Core\Util
|
||||
* @copyright 2022 Google LLC
|
||||
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
|
||||
* @link https://sitekit.withgoogle.com
|
||||
*/
|
||||
|
||||
namespace Google\Site_Kit\Core\Util;
|
||||
|
||||
use Google\Site_Kit\Context;
|
||||
use Google\Site_Kit\Core\Permissions\Permissions;
|
||||
use Google\Site_Kit\Core\REST_API\REST_Route;
|
||||
use WP_REST_Server;
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Response;
|
||||
|
||||
/**
|
||||
* Class for handling entity search REST routes.
|
||||
*
|
||||
* @since 1.68.0
|
||||
* @access private
|
||||
* @ignore
|
||||
*/
|
||||
class REST_Entity_Search_Controller {
|
||||
|
||||
/**
|
||||
* Plugin context.
|
||||
*
|
||||
* @since 1.68.0
|
||||
* @var Context
|
||||
*/
|
||||
private $context;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since 1.68.0
|
||||
*
|
||||
* @param Context $context Plugin context.
|
||||
*/
|
||||
public function __construct( Context $context ) {
|
||||
$this->context = $context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers functionality through WordPress hooks.
|
||||
*
|
||||
* @since 1.68.0
|
||||
*/
|
||||
public function register() {
|
||||
add_filter(
|
||||
'googlesitekit_rest_routes',
|
||||
function ( $routes ) {
|
||||
return array_merge( $routes, $this->get_rest_routes() );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets REST route instances.
|
||||
*
|
||||
* @since 1.68.0
|
||||
*
|
||||
* @return REST_Route[] List of REST_Route objects.
|
||||
*/
|
||||
protected function get_rest_routes() {
|
||||
$can_search = function() {
|
||||
return current_user_can( Permissions::AUTHENTICATE ) || current_user_can( Permissions::VIEW_SHARED_DASHBOARD );
|
||||
};
|
||||
|
||||
return array(
|
||||
new REST_Route(
|
||||
'core/search/data/entity-search',
|
||||
array(
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => function( WP_REST_Request $request ) {
|
||||
$query = rawurldecode( $request['query'] );
|
||||
$entities = array();
|
||||
if ( filter_var( $query, FILTER_VALIDATE_URL ) ) {
|
||||
$entity = $this->context->get_reference_entity_from_url( $query );
|
||||
if ( $entity && $entity->get_id() ) {
|
||||
$entities = array(
|
||||
array(
|
||||
'id' => $entity->get_id(),
|
||||
'title' => $entity->get_title(),
|
||||
'url' => $entity->get_url(),
|
||||
'type' => $entity->get_type(),
|
||||
),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$args = array(
|
||||
'posts_per_page' => 10,
|
||||
'google-site-kit' => 1,
|
||||
's' => $query,
|
||||
'no_found_rows' => true,
|
||||
'update_post_meta_cache' => false,
|
||||
'update_post_term_cache' => false,
|
||||
'post_status' => array( 'publish' ),
|
||||
);
|
||||
|
||||
$posts = ( new \WP_Query( $args ) )->posts;
|
||||
|
||||
if ( ! empty( $posts ) ) {
|
||||
$entities = array_map(
|
||||
function( $post ) {
|
||||
$entity = Entity_Factory::create_entity_for_post( $post, 1 );
|
||||
return array(
|
||||
'id' => $entity->get_id(),
|
||||
'title' => $entity->get_title(),
|
||||
'url' => $entity->get_url(),
|
||||
'type' => $entity->get_type(),
|
||||
);
|
||||
},
|
||||
$posts
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return new WP_REST_Response( $entities );
|
||||
},
|
||||
'permission_callback' => $can_search,
|
||||
),
|
||||
),
|
||||
array(
|
||||
'args' => array(
|
||||
'query' => array(
|
||||
'type' => 'string',
|
||||
'description' => __( 'Text content to search for.', 'google-site-kit' ),
|
||||
'required' => true,
|
||||
),
|
||||
),
|
||||
)
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Google\Site_Kit\Core\Util\Remote_Features
|
||||
*
|
||||
* @package Google\Site_Kit
|
||||
* @copyright 2024 Google LLC
|
||||
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
|
||||
* @link https://sitekit.withgoogle.com
|
||||
*/
|
||||
|
||||
namespace Google\Site_Kit\Core\Util;
|
||||
|
||||
use Google\Site_Kit\Core\Authentication\Authentication;
|
||||
use Google\Site_Kit\Core\Authentication\Credentials;
|
||||
use Google\Site_Kit\Core\Authentication\Google_Proxy;
|
||||
use Google\Site_Kit\Core\Storage\Options;
|
||||
use WP_Error;
|
||||
|
||||
/**
|
||||
* Class handling the fetching of Site Kit's currently
|
||||
* enabled features remotely via the Site Kit service.
|
||||
*
|
||||
* @since 1.118.0
|
||||
* @access private
|
||||
* @ignore
|
||||
*/
|
||||
final class Remote_Features {
|
||||
|
||||
use Method_Proxy_Trait;
|
||||
|
||||
/**
|
||||
* Option key in options table to store remote features.
|
||||
*/
|
||||
const OPTION = 'googlesitekitpersistent_remote_features';
|
||||
|
||||
/**
|
||||
* Options instance.
|
||||
*
|
||||
* @since 1.118.0
|
||||
*
|
||||
* @var Options
|
||||
*/
|
||||
private $options;
|
||||
|
||||
/**
|
||||
* Authentication instance.
|
||||
*
|
||||
* @since 1.118.0
|
||||
*
|
||||
* @var Authentication
|
||||
*/
|
||||
private $authentication;
|
||||
|
||||
/**
|
||||
* OAuth credentials instance.
|
||||
*
|
||||
* @since 1.118.0
|
||||
*
|
||||
* @var Credentials
|
||||
*/
|
||||
private $credentials;
|
||||
|
||||
/**
|
||||
* Google_Proxy instance.
|
||||
*
|
||||
* @since 1.118.0
|
||||
*
|
||||
* @var Google_Proxy
|
||||
*/
|
||||
protected $google_proxy;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since 1.118.0
|
||||
*
|
||||
* @param Options $options Options instance.
|
||||
* @param Authentication $authentication Authentication instance.
|
||||
*/
|
||||
public function __construct( Options $options, Authentication $authentication ) {
|
||||
$this->options = $options;
|
||||
$this->authentication = $authentication;
|
||||
$this->credentials = $authentication->credentials();
|
||||
$this->google_proxy = $authentication->get_google_proxy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers functionality through WordPress hooks.
|
||||
*
|
||||
* @since 1.118.0
|
||||
*/
|
||||
public function register() {
|
||||
add_filter( 'googlesitekit_is_feature_enabled', $this->get_method_proxy( 'filter_features' ), 10, 2 );
|
||||
|
||||
add_action( 'googlesitekit_cron_update_remote_features', $this->get_method_proxy( 'cron_update_remote_features' ) );
|
||||
if ( ! wp_next_scheduled( 'googlesitekit_cron_update_remote_features' ) && ! wp_installing() ) {
|
||||
wp_schedule_event( time(), 'twicedaily', 'googlesitekit_cron_update_remote_features' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters feature flags using features stored in options.
|
||||
*
|
||||
* @since 1.118.0
|
||||
*
|
||||
* @param boolean $feature_enabled Original value of the feature.
|
||||
* @param string $feature_name Feature name.
|
||||
* @return boolean State flag from options if it is available, otherwise the original value.
|
||||
*/
|
||||
private function filter_features( $feature_enabled, $feature_name ) {
|
||||
$features = $this->options->get( self::OPTION );
|
||||
|
||||
if ( isset( $features[ $feature_name ]['enabled'] ) ) {
|
||||
return filter_var( $features[ $feature_name ]['enabled'], FILTER_VALIDATE_BOOLEAN );
|
||||
}
|
||||
|
||||
return $feature_enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches remotely-controlled features from the Google Proxy server and
|
||||
* saves them in a persistent option.
|
||||
*
|
||||
* If the fetch errors or fails, the persistent option is not updated.
|
||||
*
|
||||
* @since 1.71.0
|
||||
* @since 1.118.0 Moved here from the Authentication class.
|
||||
*
|
||||
* @return array|WP_Error Array of features or a WP_Error object if the fetch errored.
|
||||
*/
|
||||
public function fetch_remote_features() {
|
||||
$remote_features_option = self::OPTION;
|
||||
$features = $this->google_proxy->get_features( $this->credentials );
|
||||
if ( ! is_wp_error( $features ) && is_array( $features ) ) {
|
||||
$this->options->set( $remote_features_option, $features );
|
||||
}
|
||||
|
||||
return $features;
|
||||
}
|
||||
|
||||
/**
|
||||
* Action that is run by a cron twice daily to fetch and cache remotely-enabled features
|
||||
* from the Google Proxy server, if Site Kit has been setup.
|
||||
*
|
||||
* @since 1.71.0
|
||||
* @since 1.118.0 Moved here from the Authentication class.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function cron_update_remote_features() {
|
||||
if ( ! $this->credentials->has() || ! $this->credentials->using_proxy() ) {
|
||||
return;
|
||||
}
|
||||
$this->fetch_remote_features();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
/**
|
||||
* Trait Google\Site_Kit\Core\Util\Requires_Javascript_Trait
|
||||
*
|
||||
* @package Google\Site_Kit\Core\Util
|
||||
* @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\Core\Util;
|
||||
|
||||
/**
|
||||
* Trait to display no javascript fallback message.
|
||||
*
|
||||
* @since 1.5.0
|
||||
* @access private
|
||||
* @ignore
|
||||
*/
|
||||
trait Requires_Javascript_Trait {
|
||||
|
||||
/**
|
||||
* Outputs a fallback message when Javascript is disabled.
|
||||
*
|
||||
* @since 1.5.0
|
||||
*/
|
||||
protected function render_noscript_html() {
|
||||
?>
|
||||
<noscript>
|
||||
<div class="googlesitekit-noscript notice notice-warning">
|
||||
<div class="mdc-layout-grid">
|
||||
<div class="mdc-layout-grid__inner">
|
||||
<div class="mdc-layout-grid__cell mdc-layout-grid__cell--span-12">
|
||||
<p class="googlesitekit-noscript__text">
|
||||
<?php
|
||||
esc_html_e( 'The Site Kit by Google plugin requires JavaScript to be enabled in your browser.', 'google-site-kit' )
|
||||
?>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</noscript>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
319
wp-content/plugins/google-site-kit/includes/Core/Util/Reset.php
Normal file
319
wp-content/plugins/google-site-kit/includes/Core/Util/Reset.php
Normal file
@@ -0,0 +1,319 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Google\Site_Kit\Core\Util\Reset
|
||||
*
|
||||
* @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\Core\Util;
|
||||
|
||||
use Google\Site_Kit\Context;
|
||||
use Google\Site_Kit\Core\Authentication\Authentication;
|
||||
use Google\Site_Kit\Core\Permissions\Permissions;
|
||||
use Google\Site_Kit\Core\REST_API\REST_Route;
|
||||
use WP_REST_Server;
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Response;
|
||||
|
||||
/**
|
||||
* Class providing functions to reset the plugin.
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @since 1.1.1 Removed delete_all_plugin_options(), delete_all_user_metas() and delete_all_transients() methods.
|
||||
* @access private
|
||||
* @ignore
|
||||
*/
|
||||
class Reset {
|
||||
|
||||
/**
|
||||
* MySQL key pattern for all Site Kit keys.
|
||||
*/
|
||||
const KEY_PATTERN = 'googlesitekit\_%';
|
||||
|
||||
/**
|
||||
* REST API endpoint.
|
||||
*/
|
||||
const REST_ROUTE = 'core/site/data/reset';
|
||||
|
||||
/**
|
||||
* Action for triggering a reset.
|
||||
*/
|
||||
const ACTION = 'googlesitekit_reset';
|
||||
|
||||
/**
|
||||
* Plugin context.
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @var Context
|
||||
*/
|
||||
private $context;
|
||||
|
||||
/**
|
||||
* Gets the URL to handle a reset action.
|
||||
*
|
||||
* @since 1.30.0
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function url() {
|
||||
return add_query_arg(
|
||||
array(
|
||||
'action' => static::ACTION,
|
||||
'nonce' => wp_create_nonce( static::ACTION ),
|
||||
),
|
||||
admin_url( 'index.php' )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @since 1.1.1 Removed $options and $transients params.
|
||||
*
|
||||
* @param Context $context Plugin context.
|
||||
*/
|
||||
public function __construct( Context $context ) {
|
||||
$this->context = $context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers functionality through WordPress hooks.
|
||||
*
|
||||
* @since 1.3.0
|
||||
*/
|
||||
public function register() {
|
||||
add_filter(
|
||||
'googlesitekit_rest_routes',
|
||||
function( $routes ) {
|
||||
return array_merge( $routes, $this->get_rest_routes() );
|
||||
}
|
||||
);
|
||||
|
||||
add_action(
|
||||
'admin_action_' . static::ACTION,
|
||||
function () {
|
||||
$this->handle_reset_action(
|
||||
$this->context->input()->filter( INPUT_GET, 'nonce' )
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes options, user stored options, transients and clears object cache for stored options.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function all() {
|
||||
$this->delete_options( 'site' );
|
||||
$this->delete_user_options( 'site' );
|
||||
$this->delete_post_meta( 'site' );
|
||||
|
||||
if ( $this->context->is_network_mode() ) {
|
||||
$this->delete_options( 'network' );
|
||||
$this->delete_user_options( 'network' );
|
||||
$this->delete_post_meta( 'network' );
|
||||
}
|
||||
|
||||
wp_cache_flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes all Site Kit options and transients.
|
||||
*
|
||||
* @since 1.3.0
|
||||
*
|
||||
* @param string $scope Scope of the deletion ('site' or 'network').
|
||||
*/
|
||||
private function delete_options( $scope ) {
|
||||
global $wpdb;
|
||||
|
||||
if ( 'site' === $scope ) {
|
||||
list ( $table_name, $column_name, $transient_prefix ) = array( $wpdb->options, 'option_name', '_transient_' );
|
||||
} elseif ( 'network' === $scope ) {
|
||||
list ( $table_name, $column_name, $transient_prefix ) = array( $wpdb->sitemeta, 'meta_key', '_site_transient_' );
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery
|
||||
$wpdb->query(
|
||||
$wpdb->prepare(
|
||||
/* phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared */
|
||||
"
|
||||
DELETE FROM {$table_name}
|
||||
WHERE {$column_name} LIKE %s
|
||||
OR {$column_name} LIKE %s
|
||||
OR {$column_name} LIKE %s
|
||||
OR {$column_name} = %s
|
||||
", /* phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared */
|
||||
static::KEY_PATTERN,
|
||||
$transient_prefix . static::KEY_PATTERN,
|
||||
$transient_prefix . 'timeout_' . static::KEY_PATTERN,
|
||||
'googlesitekit-active-modules'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes all Site Kit user options.
|
||||
*
|
||||
* @param string $scope Scope of the deletion ('site' or 'network').
|
||||
*/
|
||||
private function delete_user_options( $scope ) {
|
||||
global $wpdb;
|
||||
|
||||
if ( 'site' === $scope ) {
|
||||
$meta_prefix = $wpdb->get_blog_prefix();
|
||||
} elseif ( 'network' === $scope ) {
|
||||
$meta_prefix = '';
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery
|
||||
$wpdb->query(
|
||||
$wpdb->prepare(
|
||||
"DELETE FROM {$wpdb->usermeta} WHERE meta_key LIKE %s",
|
||||
$meta_prefix . static::KEY_PATTERN
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes all Site Kit post meta settings.
|
||||
*
|
||||
* @since 1.33.0
|
||||
*
|
||||
* @param string $scope Scope of the deletion ('site' or 'network').
|
||||
*/
|
||||
private function delete_post_meta( $scope ) {
|
||||
global $wpdb;
|
||||
|
||||
$sites = array();
|
||||
if ( 'network' === $scope ) {
|
||||
$sites = get_sites(
|
||||
array(
|
||||
'fields' => 'ids',
|
||||
'number' => 9999999,
|
||||
)
|
||||
);
|
||||
} else {
|
||||
$sites[] = get_current_blog_id();
|
||||
}
|
||||
|
||||
foreach ( $sites as $site_id ) {
|
||||
$prefix = $wpdb->get_blog_prefix( $site_id );
|
||||
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery
|
||||
$wpdb->query(
|
||||
$wpdb->prepare(
|
||||
"DELETE FROM {$prefix}postmeta WHERE `meta_key` LIKE %s", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
static::KEY_PATTERN
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets related REST routes.
|
||||
*
|
||||
* @since 1.3.0
|
||||
*
|
||||
* @return array List of REST_Route objects.
|
||||
*/
|
||||
private function get_rest_routes() {
|
||||
$can_setup = function() {
|
||||
return current_user_can( Permissions::SETUP );
|
||||
};
|
||||
|
||||
return array(
|
||||
new REST_Route(
|
||||
static::REST_ROUTE,
|
||||
array(
|
||||
array(
|
||||
'methods' => WP_REST_Server::EDITABLE,
|
||||
'callback' => function( WP_REST_Request $request ) {
|
||||
$this->all();
|
||||
$this->maybe_hard_reset();
|
||||
|
||||
// Call hooks on plugin reset. This is used to reset the ad blocking recovery notification.
|
||||
do_action( 'googlesitekit_reset' );
|
||||
|
||||
return new WP_REST_Response( true );
|
||||
},
|
||||
'permission_callback' => $can_setup,
|
||||
),
|
||||
)
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the reset admin action.
|
||||
*
|
||||
* @since 1.30.0
|
||||
*
|
||||
* @param string $nonce WP nonce for action.
|
||||
*/
|
||||
private function handle_reset_action( $nonce ) {
|
||||
if ( ! wp_verify_nonce( $nonce, static::ACTION ) ) {
|
||||
$authentication = new Authentication( $this->context );
|
||||
$authentication->invalid_nonce_error( static::ACTION );
|
||||
}
|
||||
if ( ! current_user_can( Permissions::SETUP ) ) {
|
||||
wp_die( esc_html__( 'You don’t have permissions to set up Site Kit.', 'google-site-kit' ), 403 );
|
||||
}
|
||||
|
||||
// Call hooks on plugin reset. This is used to reset the ad blocking recovery notification.
|
||||
do_action( 'googlesitekit_reset' );
|
||||
|
||||
$this->all();
|
||||
$this->maybe_hard_reset();
|
||||
|
||||
wp_safe_redirect(
|
||||
$this->context->admin_url(
|
||||
'splash',
|
||||
array(
|
||||
// Trigger client-side storage reset.
|
||||
'googlesitekit_reset_session' => 1,
|
||||
// Show reset-success notification.
|
||||
'notification' => 'reset_success',
|
||||
)
|
||||
)
|
||||
);
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs hard reset if it is enabled programmatically.
|
||||
*
|
||||
* @since 1.46.0
|
||||
*/
|
||||
public function maybe_hard_reset() {
|
||||
/**
|
||||
* Filters the hard reset option, which is `false` by default.
|
||||
*
|
||||
* By default, when Site Kit is reset it does not delete "persistent" data
|
||||
* (options prefixed with `googlesitekitpersistent_`). If this filter returns `true`,
|
||||
* all options belonging to Site Kit, including those with the above "persistent"
|
||||
* prefix, will be deleted.
|
||||
*
|
||||
* @since 1.46.0
|
||||
*
|
||||
* @param bool $hard_reset_enabled If a hard reset is enabled. `false` by default.
|
||||
*/
|
||||
$hard_reset_enabled = apply_filters( 'googlesitekit_hard_reset_enabled', false );
|
||||
if ( ! $hard_reset_enabled ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$reset_persistent = new Reset_Persistent( $this->context );
|
||||
$reset_persistent->all();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Google\Site_Kit\Core\Util\Reset_Persistent
|
||||
*
|
||||
* @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\Core\Util;
|
||||
|
||||
/**
|
||||
* Class providing functions to reset the persistent plugin settings.
|
||||
*
|
||||
* @since 1.27.0
|
||||
* @access private
|
||||
* @ignore
|
||||
*/
|
||||
class Reset_Persistent extends Reset {
|
||||
|
||||
/**
|
||||
* MySQL key pattern for all persistent Site Kit keys.
|
||||
*/
|
||||
const KEY_PATTERN = 'googlesitekitpersistent\_%';
|
||||
|
||||
/**
|
||||
* REST API endpoint.
|
||||
*/
|
||||
const REST_ROUTE = 'core/site/data/reset-persistent';
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Google\Site_Kit\Core\Util\Sanitize
|
||||
*
|
||||
* @package Google\Site_Kit\Core\Util
|
||||
* @copyright 2023 Google LLC
|
||||
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
|
||||
* @link https://sitekit.withgoogle.com
|
||||
*/
|
||||
|
||||
namespace Google\Site_Kit\Core\Util;
|
||||
|
||||
/**
|
||||
* Utility class for sanitizing data.
|
||||
*
|
||||
* @since 1.93.0
|
||||
* @access private
|
||||
* @ignore
|
||||
*/
|
||||
class Sanitize {
|
||||
|
||||
/**
|
||||
* Filters empty or non-string elements from a given array.
|
||||
*
|
||||
* @since 1.93.0
|
||||
*
|
||||
* @param array $elements Array to check.
|
||||
* @return array Empty array or a filtered array containing only non-empty strings.
|
||||
*/
|
||||
public static function sanitize_string_list( $elements = array() ) {
|
||||
if ( ! is_array( $elements ) ) {
|
||||
$elements = array( $elements );
|
||||
}
|
||||
|
||||
if ( empty( $elements ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$filtered_elements = array_filter(
|
||||
$elements,
|
||||
function( $element ) {
|
||||
return is_string( $element ) && ! empty( $element );
|
||||
}
|
||||
);
|
||||
// Avoid index gaps for filtered values.
|
||||
return array_values( $filtered_elements );
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Google\Site_Kit\Core\Util\Scopes
|
||||
*
|
||||
* @package Google\Site_Kit\Core\Util
|
||||
* @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\Core\Util;
|
||||
|
||||
/**
|
||||
* Utility class for handling generic OAuth scope functions.
|
||||
*
|
||||
* @since 1.9.0
|
||||
* @access private
|
||||
* @ignore
|
||||
*/
|
||||
class Scopes {
|
||||
|
||||
/**
|
||||
* Mapping of requested scope to satisfying scopes.
|
||||
*
|
||||
* @since 1.9.0
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $map = array(
|
||||
'https://www.googleapis.com/auth/adsense.readonly' => array(
|
||||
'https://www.googleapis.com/auth/adsense',
|
||||
),
|
||||
'https://www.googleapis.com/auth/analytics.readonly' => array(
|
||||
'requires_all' => true,
|
||||
'https://www.googleapis.com/auth/analytics',
|
||||
'https://www.googleapis.com/auth/analytics.edit',
|
||||
),
|
||||
'https://www.googleapis.com/auth/tagmanager.readonly' => array(
|
||||
'https://www.googleapis.com/auth/tagmanager.edit.containers',
|
||||
),
|
||||
'https://www.googleapis.com/auth/webmasters.readonly' => array(
|
||||
'https://www.googleapis.com/auth/webmasters',
|
||||
),
|
||||
);
|
||||
|
||||
/**
|
||||
* Tests if the given scope is satisfied by the given list of granted scopes.
|
||||
*
|
||||
* @since 1.9.0
|
||||
*
|
||||
* @param string $scope OAuth scope to test for.
|
||||
* @param string[] $granted_scopes Available OAuth scopes to test the individual scope against.
|
||||
* @return bool True if the given scope is satisfied, otherwise false.
|
||||
*/
|
||||
public static function is_satisfied_by( $scope, array $granted_scopes ) {
|
||||
if ( in_array( $scope, $granted_scopes, true ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( empty( self::$map[ $scope ] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$satisfying_scopes = array_filter( self::$map[ $scope ], 'is_string' );
|
||||
|
||||
if ( ! empty( self::$map[ $scope ]['requires_all'] ) ) {
|
||||
// Return true if all satisfying scopes are present, otherwise false.
|
||||
return ! array_diff( $satisfying_scopes, $granted_scopes );
|
||||
}
|
||||
|
||||
// Return true if any of the scopes are present, otherwise false.
|
||||
return (bool) array_intersect( $satisfying_scopes, $granted_scopes );
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if all the given scopes are satisfied by the list of granted scopes.
|
||||
*
|
||||
* @since 1.9.0
|
||||
*
|
||||
* @param string[] $scopes OAuth scopes to test.
|
||||
* @param string[] $granted_scopes OAuth scopes to test $scopes against.
|
||||
* @return bool True if all given scopes are satisfied, otherwise false.
|
||||
*/
|
||||
public static function are_satisfied_by( array $scopes, array $granted_scopes ) {
|
||||
foreach ( $scopes as $scope ) {
|
||||
if ( ! self::is_satisfied_by( $scope, $granted_scopes ) ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Google\Site_Kit\Core\Util\Sort
|
||||
*
|
||||
* @package Google\Site_Kit\Core\Util
|
||||
* @copyright 2022 Google LLC
|
||||
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
|
||||
* @link https://sitekit.withgoogle.com
|
||||
*/
|
||||
|
||||
namespace Google\Site_Kit\Core\Util;
|
||||
|
||||
/**
|
||||
* Utility class for sorting lists.
|
||||
*
|
||||
* @since 1.90.0
|
||||
* @access private
|
||||
* @ignore
|
||||
*/
|
||||
class Sort {
|
||||
/**
|
||||
* Sorts the provided list in a case-insensitive manner.
|
||||
*
|
||||
* @since 1.90.0
|
||||
*
|
||||
* @param array $list The list to sort.
|
||||
* @param string $orderby The field by which the list should be ordered by.
|
||||
*
|
||||
* @return array The sorted list.
|
||||
*/
|
||||
public static function case_insensitive_list_sort( array $list, $orderby ) {
|
||||
usort(
|
||||
$list,
|
||||
function ( $a, $b ) use ( $orderby ) {
|
||||
if ( is_array( $a ) && is_array( $b ) ) {
|
||||
return strcasecmp(
|
||||
$a[ $orderby ],
|
||||
$b[ $orderby ]
|
||||
);
|
||||
}
|
||||
|
||||
if ( is_object( $a ) && is_object( $b ) ) {
|
||||
return strcasecmp(
|
||||
$a->$orderby,
|
||||
$b->$orderby
|
||||
);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
);
|
||||
|
||||
return $list;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Google\Site_Kit\Core\Util\Synthetic_WP_Query
|
||||
*
|
||||
* @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\Core\Util;
|
||||
|
||||
use WP_Query;
|
||||
use WP_Post;
|
||||
|
||||
/**
|
||||
* Class extending WordPress core's `WP_Query` for more self-contained behavior.
|
||||
*
|
||||
* @since 1.16.0
|
||||
* @access private
|
||||
* @ignore
|
||||
*/
|
||||
final class Synthetic_WP_Query extends WP_Query {
|
||||
|
||||
/**
|
||||
* The hash of the `$query` last parsed into `$query_vars`.
|
||||
*
|
||||
* @since 1.16.0
|
||||
* @var string
|
||||
*/
|
||||
private $parsed_query_hash = '';
|
||||
|
||||
/**
|
||||
* Whether automatic 404 detection in `get_posts()` method is enabled.
|
||||
*
|
||||
* @since 1.16.0
|
||||
* @var bool
|
||||
*/
|
||||
private $enable_404_detection = false;
|
||||
|
||||
/**
|
||||
* Sets whether 404 detection in `get_posts()` method should be enabled.
|
||||
*
|
||||
* @since 1.16.0
|
||||
*
|
||||
* @param bool $enable Whether or not to enable 404 detection.
|
||||
*/
|
||||
public function enable_404_detection( $enable ) {
|
||||
$this->enable_404_detection = (bool) $enable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates object properties and sets default values.
|
||||
*
|
||||
* @since 1.16.0
|
||||
*/
|
||||
public function init() {
|
||||
parent::init();
|
||||
|
||||
$this->parsed_query_hash = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Extends `WP_Query::parse_query()` to ensure it is not unnecessarily run twice.
|
||||
*
|
||||
* @since 1.16.0
|
||||
*
|
||||
* @param string|array $query Optional. Array or string of query parameters. See `WP_Query::parse_query()`.
|
||||
*/
|
||||
public function parse_query( $query = '' ) {
|
||||
if ( ! empty( $query ) ) {
|
||||
$query_to_hash = wp_parse_args( $query );
|
||||
} elseif ( ! isset( $this->query ) ) {
|
||||
$query_to_hash = $this->query_vars;
|
||||
} else {
|
||||
$query_to_hash = $this->query;
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize
|
||||
$query_hash = md5( serialize( $query_to_hash ) );
|
||||
|
||||
// If this query was parsed before, bail early.
|
||||
if ( $query_hash === $this->parsed_query_hash ) {
|
||||
return;
|
||||
}
|
||||
|
||||
parent::parse_query( $query );
|
||||
|
||||
// Set query hash for current `$query` and `$query_vars` properties.
|
||||
$this->parsed_query_hash = $query_hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extends `WP_Query::get_posts()` to include supplemental logic such as detecting a 404 state.
|
||||
*
|
||||
* The majority of the code is a copy of `WP::handle_404()`.
|
||||
*
|
||||
* @since 1.16.0
|
||||
*
|
||||
* @return WP_Post[]|int[] Array of post objects or post IDs.
|
||||
*/
|
||||
public function get_posts() {
|
||||
$results = parent::get_posts();
|
||||
|
||||
// If 404 detection is not enabled, just return the results.
|
||||
if ( ! $this->enable_404_detection ) {
|
||||
return $results;
|
||||
}
|
||||
|
||||
// Check if this is a single paginated post query.
|
||||
if ( $this->posts && $this->is_singular() && $this->post && ! empty( $this->query_vars['page'] ) ) {
|
||||
// If the post is actually paged and the 'page' query var is within bounds, it's all good.
|
||||
$next = '<!--nextpage-->';
|
||||
if ( false !== strpos( $this->post->post_content, $next ) && (int) trim( $this->query_vars['page'], '/' ) <= ( substr_count( $this->post->post_content, $next ) + 1 ) ) {
|
||||
return $results;
|
||||
}
|
||||
|
||||
// Otherwise, this query is out of bounds, so set a 404.
|
||||
$this->set_404();
|
||||
return $results;
|
||||
}
|
||||
|
||||
// If no posts were found, this is technically a 404.
|
||||
if ( ! $this->posts ) {
|
||||
// If this is a paginated query (i.e. out of bounds), always consider it a 404.
|
||||
if ( $this->is_paged() ) {
|
||||
$this->set_404();
|
||||
return $results;
|
||||
}
|
||||
|
||||
// If this is an author archive, don't consider it a 404 if the author exists.
|
||||
if ( $this->is_author() ) {
|
||||
$author = $this->get( 'author' );
|
||||
if ( is_numeric( $author ) && $author > 0 && is_user_member_of_blog( $author ) ) {
|
||||
return $results;
|
||||
}
|
||||
}
|
||||
|
||||
// If this is a valid taxonomy or post type archive, don't consider it a 404.
|
||||
if ( ( $this->is_category() || $this->is_tag() || $this->is_tax() || $this->is_post_type_archive() ) && $this->get_queried_object() ) {
|
||||
return $results;
|
||||
}
|
||||
|
||||
// If this is a search results page or the home index, don't consider it a 404.
|
||||
if ( $this->is_home() || $this->is_search() ) {
|
||||
return $results;
|
||||
}
|
||||
|
||||
// Otherwise, set a 404.
|
||||
$this->set_404();
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
}
|
||||
174
wp-content/plugins/google-site-kit/includes/Core/Util/URL.php
Normal file
174
wp-content/plugins/google-site-kit/includes/Core/Util/URL.php
Normal file
@@ -0,0 +1,174 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Google\Site_Kit\Core\Util\URL
|
||||
*
|
||||
* @package Google\Site_Kit\Core\Util
|
||||
* @copyright 2022 Google LLC
|
||||
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
|
||||
* @link https://sitekit.withgoogle.com
|
||||
*/
|
||||
|
||||
namespace Google\Site_Kit\Core\Util;
|
||||
|
||||
/**
|
||||
* Class for custom URL parsing methods.
|
||||
*
|
||||
* @since 1.84.0
|
||||
* @access private
|
||||
* @ignore
|
||||
*/
|
||||
class URL {
|
||||
|
||||
/**
|
||||
* Prefix for Punycode-encoded hostnames.
|
||||
*/
|
||||
const PUNYCODE_PREFIX = 'xn--';
|
||||
|
||||
/**
|
||||
* Parses URLs with UTF-8 multi-byte characters,
|
||||
* otherwise similar to `wp_parse_url()`.
|
||||
*
|
||||
* @since 1.84.0
|
||||
*
|
||||
* @param string $url The URL to parse.
|
||||
* @param int $component The specific component to retrieve. Use one of the PHP
|
||||
* predefined constants to specify which one.
|
||||
* Defaults to -1 (= return all parts as an array).
|
||||
* @return mixed False on parse failure; Array of URL components on success;
|
||||
* When a specific component has been requested: null if the component
|
||||
* doesn't exist in the given URL; a string or - in the case of
|
||||
* PHP_URL_PORT - integer when it does. See parse_url()'s return values.
|
||||
*/
|
||||
public static function parse( $url, $component = -1 ) {
|
||||
$url = (string) $url;
|
||||
|
||||
if ( mb_strlen( $url, 'UTF-8' ) === strlen( $url ) ) {
|
||||
return wp_parse_url( $url, $component );
|
||||
}
|
||||
|
||||
$to_unset = array();
|
||||
if ( '//' === mb_substr( $url, 0, 2 ) ) {
|
||||
$to_unset[] = 'scheme';
|
||||
$url = 'placeholder:' . $url;
|
||||
} elseif ( '/' === mb_substr( $url, 0, 1 ) ) {
|
||||
$to_unset[] = 'scheme';
|
||||
$to_unset[] = 'host';
|
||||
$url = 'placeholder://placeholder' . $url;
|
||||
}
|
||||
|
||||
$parts = self::mb_parse_url( $url );
|
||||
|
||||
if ( false === $parts ) {
|
||||
// Parsing failure.
|
||||
return $parts;
|
||||
}
|
||||
|
||||
// Remove the placeholder values.
|
||||
foreach ( $to_unset as $key ) {
|
||||
unset( $parts[ $key ] );
|
||||
}
|
||||
|
||||
return _get_component_from_parsed_url_array( $parts, $component );
|
||||
}
|
||||
|
||||
/**
|
||||
* Replacement for parse_url which is UTF-8 multi-byte character aware.
|
||||
*
|
||||
* @since 1.84.0
|
||||
*
|
||||
* @param string $url The URL to parse.
|
||||
* @return mixed False on parse failure; Array of URL components on success
|
||||
*/
|
||||
private static function mb_parse_url( $url ) {
|
||||
$enc_url = preg_replace_callback(
|
||||
'%[^:/@?&=#]+%usD',
|
||||
function ( $matches ) {
|
||||
return rawurlencode( $matches[0] );
|
||||
},
|
||||
$url
|
||||
);
|
||||
|
||||
$parts = parse_url( $enc_url ); // phpcs:ignore WordPress.WP.AlternativeFunctions.parse_url_parse_url
|
||||
|
||||
if ( false === $parts ) {
|
||||
return $parts;
|
||||
}
|
||||
|
||||
foreach ( $parts as $name => $value ) {
|
||||
$parts[ $name ] = urldecode( $value );
|
||||
}
|
||||
|
||||
return $parts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Permutes site URL to cover all different variants of it (not considering the path).
|
||||
*
|
||||
* @since 1.99.0
|
||||
*
|
||||
* @param string $site_url Site URL to get permutations for.
|
||||
* @return array List of permutations.
|
||||
*/
|
||||
public static function permute_site_url( $site_url ) {
|
||||
$hostname = self::parse( $site_url, PHP_URL_HOST );
|
||||
$path = self::parse( $site_url, PHP_URL_PATH );
|
||||
|
||||
return array_reduce(
|
||||
self::permute_site_hosts( $hostname ),
|
||||
function ( $urls, $host ) use ( $path ) {
|
||||
$host_with_path = $host . $path;
|
||||
array_push( $urls, "https://$host_with_path", "http://$host_with_path" );
|
||||
return $urls;
|
||||
},
|
||||
array()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates common variations of the given hostname.
|
||||
*
|
||||
* Returns a list of hostnames that includes:
|
||||
* - (if IDN) in Punycode encoding
|
||||
* - (if IDN) in Unicode encoding
|
||||
* - with and without www. subdomain (including IDNs)
|
||||
*
|
||||
* @since 1.99.0
|
||||
*
|
||||
* @param string $hostname Hostname to generate variations of.
|
||||
* @return string[] Hostname variations.
|
||||
*/
|
||||
public static function permute_site_hosts( $hostname ) {
|
||||
// See \Requests_IDNAEncoder::is_ascii.
|
||||
$is_ascii = preg_match( '/(?:[^\x00-\x7F])/', $hostname ) !== 1;
|
||||
$is_www = 0 === strpos( $hostname, 'www.' );
|
||||
// Normalize hostname without www.
|
||||
$hostname = $is_www ? substr( $hostname, strlen( 'www.' ) ) : $hostname;
|
||||
$hosts = array( $hostname, "www.$hostname" );
|
||||
|
||||
try {
|
||||
// An ASCII hostname can only be non-IDN or punycode-encoded.
|
||||
if ( $is_ascii ) {
|
||||
// If the hostname is in punycode encoding, add the decoded version to the list of hosts.
|
||||
if ( 0 === strpos( $hostname, self::PUNYCODE_PREFIX ) || false !== strpos( $hostname, '.' . self::PUNYCODE_PREFIX ) ) {
|
||||
// Ignoring phpcs here, and not passing the variant so that the correct default can be selected by PHP based on the
|
||||
// version. INTL_IDNA_VARIANT_UTS46 for PHP>=7.4, INTL_IDNA_VARIANT_2003 for PHP<7.4.
|
||||
// phpcs:ignore PHPCompatibility.ParameterValues.NewIDNVariantDefault.NotSet
|
||||
$host_decoded = idn_to_utf8( $hostname );
|
||||
array_push( $hosts, $host_decoded, "www.$host_decoded" );
|
||||
}
|
||||
} else {
|
||||
// If it's not ASCII, then add the punycode encoded version.
|
||||
// Ignoring phpcs here, and not passing the variant so that the correct default can be selected by PHP based on the
|
||||
// version. INTL_IDNA_VARIANT_UTS46 for PHP>=7.4, INTL_IDNA_VARIANT_2003 for PHP<7.4.
|
||||
// phpcs:ignore PHPCompatibility.ParameterValues.NewIDNVariantDefault.NotSet
|
||||
$host_encoded = idn_to_ascii( $hostname );
|
||||
array_push( $hosts, $host_encoded, "www.$host_encoded" );
|
||||
}
|
||||
} catch ( Exception $exception ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
return $hosts;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Google\Site_Kit\Core\Util\Uninstallation
|
||||
*
|
||||
* @package Google\Site_Kit\Core\Util
|
||||
* @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\Core\Util;
|
||||
|
||||
use Google\Site_Kit\Context;
|
||||
use Google\Site_Kit\Core\Storage\Options;
|
||||
use Google\Site_Kit\Core\Storage\Encrypted_Options;
|
||||
use Google\Site_Kit\Core\Authentication\Credentials;
|
||||
use Google\Site_Kit\Core\Authentication\Google_Proxy;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Utility class for handling uninstallation of the plugin.
|
||||
*
|
||||
* @since 1.20.0
|
||||
* @access private
|
||||
* @ignore
|
||||
*/
|
||||
class Uninstallation {
|
||||
|
||||
/**
|
||||
* Plugin context.
|
||||
*
|
||||
* @since 1.20.0
|
||||
* @var Context
|
||||
*/
|
||||
private $context;
|
||||
|
||||
/**
|
||||
* Options instance.
|
||||
*
|
||||
* @since 1.20.0
|
||||
* @var Options
|
||||
*/
|
||||
private $options;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* This class and its logic must be instantiated early in the WordPress
|
||||
* bootstrap lifecycle because the 'uninstall.php' script runs decoupled
|
||||
* from regular action hooks like 'init'.
|
||||
*
|
||||
* @since 1.20.0
|
||||
*
|
||||
* @param Context $context Plugin context.
|
||||
* @param Options $options Optional. Options instance. Default is a new instance.
|
||||
*/
|
||||
public function __construct(
|
||||
Context $context,
|
||||
Options $options = null
|
||||
) {
|
||||
$this->context = $context;
|
||||
$this->options = $options ?: new Options( $this->context );
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers functionality through WordPress hooks.
|
||||
*
|
||||
* @since 1.20.0
|
||||
*/
|
||||
public function register() {
|
||||
add_action(
|
||||
'googlesitekit_uninstallation',
|
||||
function() {
|
||||
$this->uninstall();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs necessary logic for uninstallation of the plugin.
|
||||
*
|
||||
* If connected to the proxy, it will issue a request to unregister the site.
|
||||
*
|
||||
* @since 1.20.0
|
||||
*/
|
||||
private function uninstall() {
|
||||
$credentials = new Credentials( new Encrypted_Options( $this->options ) );
|
||||
if ( $credentials->has() && $credentials->using_proxy() ) {
|
||||
$google_proxy = new Google_Proxy( $this->context );
|
||||
$google_proxy->unregister_site( $credentials );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
/**
|
||||
* Trait Google\Site_Kit\Core\Util\WP_Context_Switcher_Trait
|
||||
*
|
||||
* @package Google\Site_Kit\Core\Util
|
||||
* @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\Core\Util;
|
||||
|
||||
/**
|
||||
* Trait for temporarily switching WordPress context, e.g. from admin to frontend.
|
||||
*
|
||||
* @since 1.16.0
|
||||
* @access private
|
||||
* @ignore
|
||||
*/
|
||||
trait WP_Context_Switcher_Trait {
|
||||
|
||||
/**
|
||||
* Switches to WordPress frontend context if necessary.
|
||||
*
|
||||
* Context is only switched if WordPress is not already in frontend context. Context should only ever be switched
|
||||
* temporarily. Call the returned closure as soon as possible after to restore the original context.
|
||||
*
|
||||
* @since 1.16.0
|
||||
*
|
||||
* @return callable Closure that restores context.
|
||||
*/
|
||||
protected static function with_frontend_context() {
|
||||
$restore = self::get_restore_current_screen_closure();
|
||||
|
||||
if ( ! is_admin() ) {
|
||||
return $restore;
|
||||
}
|
||||
|
||||
self::switch_current_screen( 'front' );
|
||||
return $restore;
|
||||
}
|
||||
|
||||
/**
|
||||
* Switches the current WordPress screen via the given screen ID or hook name.
|
||||
*
|
||||
* @since 1.16.0
|
||||
*
|
||||
* @param string $screen_id WordPress screen ID.
|
||||
*/
|
||||
private static function switch_current_screen( $screen_id ) {
|
||||
global $current_screen;
|
||||
|
||||
require_once ABSPATH . 'wp-admin/includes/class-wp-screen.php';
|
||||
require_once ABSPATH . 'wp-admin/includes/screen.php';
|
||||
|
||||
$current_screen = \WP_Screen::get( $screen_id ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the closure to restore the current screen.
|
||||
*
|
||||
* Calling the closure will restore the `$current_screen` global to what it was set to at the time of calling
|
||||
* this method.
|
||||
*
|
||||
* @since 1.16.0
|
||||
*
|
||||
* @return callable Closure that restores context.
|
||||
*/
|
||||
private static function get_restore_current_screen_closure() {
|
||||
global $current_screen;
|
||||
|
||||
$original_screen = $current_screen;
|
||||
|
||||
return static function() use ( $original_screen ) {
|
||||
global $current_screen;
|
||||
|
||||
$current_screen = $original_screen; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,288 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Google\Site_Kit\Core\Util\WP_Query_Factory
|
||||
*
|
||||
* @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\Core\Util;
|
||||
|
||||
use WP_Query;
|
||||
|
||||
/**
|
||||
* Class creating `WP_Query` instances.
|
||||
*
|
||||
* @since 1.15.0
|
||||
* @access private
|
||||
* @ignore
|
||||
*/
|
||||
final class WP_Query_Factory {
|
||||
use WP_Context_Switcher_Trait;
|
||||
|
||||
/**
|
||||
* Creates a `WP_Query` instance to use for a given URL.
|
||||
*
|
||||
* The `WP_Query` instance returned is initialized with the correct query arguments, but the actual query will not
|
||||
* have run yet. The `WP_Query::get_posts()` method should be used to do that.
|
||||
*
|
||||
* This is an expensive function that works similarly to WordPress core's `url_to_postid()` function, however also
|
||||
* covering non-post URLs. It follows logic used in `WP::parse_request()` to cover the other kinds of URLs. The
|
||||
* majority of the code is a direct copy of certain parts of these functions.
|
||||
*
|
||||
* @since 1.15.0
|
||||
*
|
||||
* @param string $url URL to get WordPress query object for.
|
||||
* @return WP_Query|null WordPress query instance, or null if unable to parse query from URL.
|
||||
*/
|
||||
public static function from_url( $url ) {
|
||||
$url = self::normalize_url( $url );
|
||||
if ( empty( $url ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$url_path_vars = self::get_url_path_vars( $url );
|
||||
$url_query_vars = self::get_url_query_vars( $url );
|
||||
|
||||
$query_args = self::parse_wp_query_args( $url_path_vars, $url_query_vars );
|
||||
|
||||
$restore_context = self::with_frontend_context();
|
||||
|
||||
// Return extended version of `WP_Query` with self-contained 404 detection.
|
||||
$query = new Synthetic_WP_Query();
|
||||
$query->parse_query( $query_args );
|
||||
$query->enable_404_detection( true );
|
||||
|
||||
$restore_context();
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes the URL for further processing.
|
||||
*
|
||||
* @since 1.15.0
|
||||
*
|
||||
* @param string $url URL to normalize.
|
||||
* @return string Normalized URL, or empty string if URL is irrelevant for parsing into `WP_Query` arguments.
|
||||
*/
|
||||
private static function normalize_url( $url ) {
|
||||
global $wp_rewrite;
|
||||
|
||||
$url_host = str_replace( 'www.', '', URL::parse( $url, PHP_URL_HOST ) );
|
||||
$home_url_host = str_replace( 'www.', '', URL::parse( home_url(), PHP_URL_HOST ) );
|
||||
|
||||
// Bail early if the URL does not belong to this site.
|
||||
if ( $url_host && $url_host !== $home_url_host ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Strip 'index.php/' if we're not using path info permalinks.
|
||||
if ( ! $wp_rewrite->using_index_permalinks() ) {
|
||||
$url = str_replace( $wp_rewrite->index . '/', '', $url );
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the path segment of a URL to get variables based on WordPress rewrite rules.
|
||||
*
|
||||
* The variables returned from this method are not necessarily all relevant for a `WP_Query`, they will still need
|
||||
* to go through sanitization against the available public query vars from WordPress.
|
||||
*
|
||||
* This code is mostly a partial copy of `WP::parse_request()` which is used to parse the current request URL
|
||||
* into variables in a similar way.
|
||||
*
|
||||
* @since 1.15.0
|
||||
*
|
||||
* @param string $url URL to parse path vars from.
|
||||
* @return array Associative array of path vars.
|
||||
*/
|
||||
private static function get_url_path_vars( $url ) {
|
||||
global $wp_rewrite;
|
||||
|
||||
$url_path = URL::parse( $url, PHP_URL_PATH );
|
||||
|
||||
// Strip potential home URL path segment from URL path.
|
||||
$home_path = untrailingslashit( URL::parse( home_url( '/' ), PHP_URL_PATH ) );
|
||||
if ( ! empty( $home_path ) ) {
|
||||
$url_path = substr( $url_path, strlen( $home_path ) );
|
||||
}
|
||||
|
||||
// Strip leading and trailing slashes.
|
||||
$url_path = trim( $url_path, '/' );
|
||||
|
||||
// Fetch the rewrite rules.
|
||||
$rewrite = $wp_rewrite->wp_rewrite_rules();
|
||||
|
||||
// Match path against rewrite rules.
|
||||
$matched_rule = '';
|
||||
$query = '';
|
||||
$matches = array();
|
||||
if ( empty( $url_path ) || $url_path === $wp_rewrite->index ) {
|
||||
if ( isset( $rewrite['$'] ) ) {
|
||||
$matched_rule = '$';
|
||||
$query = $rewrite['$'];
|
||||
$matches = array( '' );
|
||||
}
|
||||
} else {
|
||||
foreach ( (array) $rewrite as $match => $query ) {
|
||||
if ( preg_match( "#^$match#", $url_path, $matches ) ) {
|
||||
if ( $wp_rewrite->use_verbose_page_rules && preg_match( '/pagename=\$matches\[([0-9]+)\]/', $query, $varmatch ) ) {
|
||||
// This is a verbose page match, let's check to be sure about it.
|
||||
// We'll rely 100% on WP core functions here.
|
||||
// phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions
|
||||
$page = get_page_by_path( $matches[ $varmatch[1] ] );
|
||||
if ( ! $page ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$post_status_obj = get_post_status_object( $page->post_status );
|
||||
if ( ! $post_status_obj->public && ! $post_status_obj->protected
|
||||
&& ! $post_status_obj->private && $post_status_obj->exclude_from_search ) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$matched_rule = $match;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If rewrite rules matched, populate $url_path_vars.
|
||||
$url_path_vars = array();
|
||||
if ( $matched_rule ) {
|
||||
// Trim the query of everything up to the '?'.
|
||||
$query = preg_replace( '!^.+\?!', '', $query );
|
||||
|
||||
// Substitute the substring matches into the query.
|
||||
$query = addslashes( \WP_MatchesMapRegex::apply( $query, $matches ) );
|
||||
|
||||
parse_str( $query, $url_path_vars );
|
||||
}
|
||||
|
||||
return $url_path_vars;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the query segment of a URL to get variables.
|
||||
*
|
||||
* The variables returned from this method are not necessarily all relevant for a `WP_Query`, they will still need
|
||||
* to go through sanitization against the available public query vars from WordPress.
|
||||
*
|
||||
* @since 1.15.0
|
||||
*
|
||||
* @param string $url URL to parse query vars from.
|
||||
* @return array Associative array of query vars.
|
||||
*/
|
||||
private static function get_url_query_vars( $url ) {
|
||||
$url_query = URL::parse( $url, PHP_URL_QUERY );
|
||||
|
||||
$url_query_vars = array();
|
||||
if ( $url_query ) {
|
||||
parse_str( $url_query, $url_query_vars );
|
||||
}
|
||||
|
||||
return $url_query_vars;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns arguments for a `WP_Query` instance based on URL path vars and URL query vars.
|
||||
*
|
||||
* This method essentially sanitizes the passed vars, allowing only WordPress public query vars to be used as
|
||||
* actual arguments for `WP_Query`. When combining URL path vars and URL query vars, the latter take precedence.
|
||||
*
|
||||
* This code is mostly a partial copy of `WP::parse_request()` which is used to parse the current request URL
|
||||
* into query arguments in a similar way.
|
||||
*
|
||||
* @since 1.15.0
|
||||
*
|
||||
* @param array $url_path_vars Associative array as returned from {@see WP_Query_Factory::get_url_path_vars()}.
|
||||
* @param array $url_query_vars Associative array as returned from {@see WP_Query_Factory::get_url_query_vars()}.
|
||||
* @return array Associative array of arguments to pass to a `WP_Query` instance.
|
||||
*/
|
||||
private static function parse_wp_query_args( array $url_path_vars, array $url_query_vars ) {
|
||||
global $wp;
|
||||
|
||||
// Determine available post type query vars.
|
||||
$post_type_query_vars = array();
|
||||
foreach ( get_post_types( array(), 'objects' ) as $post_type => $post_type_obj ) {
|
||||
if ( is_post_type_viewable( $post_type_obj ) && $post_type_obj->query_var ) {
|
||||
$post_type_query_vars[ $post_type_obj->query_var ] = $post_type;
|
||||
}
|
||||
}
|
||||
|
||||
// Depending on whether WordPress already parsed the main request (and thus filtered 'query_vars'), we should
|
||||
// either manually trigger the filter or not.
|
||||
if ( did_action( 'parse_request' ) ) {
|
||||
$public_query_vars = $wp->public_query_vars;
|
||||
} else {
|
||||
$public_query_vars = apply_filters( 'query_vars', $wp->public_query_vars );
|
||||
}
|
||||
|
||||
// Populate `WP_Query` arguments.
|
||||
$query_args = array();
|
||||
foreach ( $public_query_vars as $wpvar ) {
|
||||
if ( isset( $url_query_vars[ $wpvar ] ) ) {
|
||||
$query_args[ $wpvar ] = $url_query_vars[ $wpvar ];
|
||||
} elseif ( isset( $url_path_vars[ $wpvar ] ) ) {
|
||||
$query_args[ $wpvar ] = $url_path_vars[ $wpvar ];
|
||||
}
|
||||
|
||||
if ( ! empty( $query_args[ $wpvar ] ) ) {
|
||||
if ( ! is_array( $query_args[ $wpvar ] ) ) {
|
||||
$query_args[ $wpvar ] = (string) $query_args[ $wpvar ];
|
||||
} else {
|
||||
foreach ( $query_args[ $wpvar ] as $key => $value ) {
|
||||
if ( is_scalar( $value ) ) {
|
||||
$query_args[ $wpvar ][ $key ] = (string) $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( isset( $post_type_query_vars[ $wpvar ] ) ) {
|
||||
$query_args['post_type'] = $post_type_query_vars[ $wpvar ];
|
||||
$query_args['name'] = $query_args[ $wpvar ];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Convert urldecoded spaces back into '+'.
|
||||
foreach ( get_taxonomies( array(), 'objects' ) as $taxonomy => $taxonomy_obj ) {
|
||||
if ( $taxonomy_obj->query_var && isset( $query_args[ $taxonomy_obj->query_var ] ) ) {
|
||||
$query_args[ $taxonomy_obj->query_var ] = str_replace( ' ', '+', $query_args[ $taxonomy_obj->query_var ] );
|
||||
}
|
||||
}
|
||||
|
||||
// Don't allow non-publicly queryable taxonomies to be queried from the front end.
|
||||
foreach ( get_taxonomies( array( 'publicly_queryable' => false ), 'objects' ) as $taxonomy => $t ) {
|
||||
if ( isset( $query_args['taxonomy'] ) && $taxonomy === $query_args['taxonomy'] ) {
|
||||
unset( $query_args['taxonomy'], $query_args['term'] );
|
||||
}
|
||||
}
|
||||
|
||||
// Limit publicly queried post_types to those that are 'publicly_queryable'.
|
||||
if ( isset( $query_args['post_type'] ) ) {
|
||||
$queryable_post_types = get_post_types( array( 'publicly_queryable' => true ) );
|
||||
if ( ! is_array( $query_args['post_type'] ) ) {
|
||||
if ( ! in_array( $query_args['post_type'], $queryable_post_types, true ) ) {
|
||||
unset( $query_args['post_type'] );
|
||||
}
|
||||
} else {
|
||||
$query_args['post_type'] = array_intersect( $query_args['post_type'], $queryable_post_types );
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve conflicts between posts with numeric slugs and date archive queries.
|
||||
$query_args = wp_resolve_numeric_slug_conflicts( $query_args );
|
||||
|
||||
// This is a WordPress core filter applied here to allow for the same modifications (e.g. for post formats).
|
||||
$query_args = apply_filters( 'request', $query_args );
|
||||
|
||||
return $query_args;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user