comments feeds, keyed by their regex pattern.
*/
$comments_rewrite = apply_filters( 'comments_rewrite_rules', $comments_rewrite );
// Search rewrite rules.
$search_structure = $this->get_search_permastruct();
$search_rewrite = $this->generate_rewrite_rules( $search_structure, EP_SEARCH );
/**
* Filters rewrite rules used for search archives.
*
* Likely search-related archives include `/search/search+query/` as well as
* pagination and feed paths for a search.
*
* @since 1.5.0
*
* @param string[] $search_rewrite Array of rewrite rules for search queries, keyed by their regex pattern.
*/
$search_rewrite = apply_filters( 'search_rewrite_rules', $search_rewrite );
// Author rewrite rules.
$author_rewrite = $this->generate_rewrite_rules( $this->get_author_permastruct(), EP_AUTHORS );
/**
* Filters rewrite rules used for author archives.
*
* Likely author archives would include `/author/author-name/`, as well as
* pagination and feed paths for author archives.
*
* @since 1.5.0
*
* @param string[] $author_rewrite Array of rewrite rules for author archives, keyed by their regex pattern.
*/
$author_rewrite = apply_filters( 'author_rewrite_rules', $author_rewrite );
// Pages rewrite rules.
$page_rewrite = $this->page_rewrite_rules();
/**
* Filters rewrite rules used for "page" post type archives.
*
* @since 1.5.0
*
* @param string[] $page_rewrite Array of rewrite rules for the "page" post type, keyed by their regex pattern.
*/
$page_rewrite = apply_filters( 'page_rewrite_rules', $page_rewrite );
// Extra permastructs.
foreach ( $this->extra_permastructs as $permastructname => $struct ) {
if ( is_array( $struct ) ) {
if ( count( $struct ) === 2 ) {
$rules = $this->generate_rewrite_rules( $struct[0], $struct[1] );
} else {
$rules = $this->generate_rewrite_rules( $struct['struct'], $struct['ep_mask'], $struct['paged'], $struct['feed'], $struct['forcomments'], $struct['walk_dirs'], $struct['endpoints'] );
}
} else {
$rules = $this->generate_rewrite_rules( $struct );
}
/**
* Filters rewrite rules used for individual permastructs.
*
* The dynamic portion of the hook name, `$permastructname`, refers
* to the name of the registered permastruct.
*
* Possible hook names include:
*
* - `category_rewrite_rules`
* - `post_format_rewrite_rules`
* - `post_tag_rewrite_rules`
*
* @since 3.1.0
*
* @param string[] $rules Array of rewrite rules generated for the current permastruct, keyed by their regex pattern.
*/
$rules = apply_filters( "{$permastructname}_rewrite_rules", $rules );
if ( 'post_tag' === $permastructname ) {
/**
* Filters rewrite rules used specifically for Tags.
*
* @since 2.3.0
* @deprecated 3.1.0 Use {@see 'post_tag_rewrite_rules'} instead.
*
* @param string[] $rules Array of rewrite rules generated for tags, keyed by their regex pattern.
*/
$rules = apply_filters_deprecated( 'tag_rewrite_rules', array( $rules ), '3.1.0', 'post_tag_rewrite_rules' );
}
$this->extra_rules_top = array_merge( $this->extra_rules_top, $rules );
}
// Put them together.
if ( $this->use_verbose_page_rules ) {
$this->rules = array_merge( $this->extra_rules_top, $robots_rewrite, $favicon_rewrite, $deprecated_files, $registration_pages, $root_rewrite, $comments_rewrite, $search_rewrite, $author_rewrite, $date_rewrite, $page_rewrite, $post_rewrite, $this->extra_rules );
} else {
$this->rules = array_merge( $this->extra_rules_top, $robots_rewrite, $favicon_rewrite, $deprecated_files, $registration_pages, $root_rewrite, $comments_rewrite, $search_rewrite, $author_rewrite, $date_rewrite, $post_rewrite, $page_rewrite, $this->extra_rules );
}
/**
* Fires after the rewrite rules are generated.
*
* @since 1.5.0
*
* @param WP_Rewrite $wp_rewrite Current WP_Rewrite instance (passed by reference).
*/
do_action_ref_array( 'generate_rewrite_rules', array( &$this ) );
/**
* Filters the full set of generated rewrite rules.
*
* @since 1.5.0
*
* @param string[] $rules The compiled array of rewrite rules, keyed by their regex pattern.
*/
$this->rules = apply_filters( 'rewrite_rules_array', $this->rules );
return $this->rules;
}
/**
* Retrieves the rewrite rules.
*
* The difference between this method and WP_Rewrite::rewrite_rules() is that
* this method stores the rewrite rules in the 'rewrite_rules' option and retrieves
* it. This prevents having to process all of the permalinks to get the rewrite rules
* in the form of caching.
*
* @since 1.5.0
*
* @return string[] Array of rewrite rules keyed by their regex pattern.
*/
public function wp_rewrite_rules() {
$this->rules = get_option( 'rewrite_rules' );
if ( empty( $this->rules ) ) {
$this->refresh_rewrite_rules();
}
return $this->rules;
}
/**
* Refreshes the rewrite rules, saving the fresh value to the database.
* If the `wp_loaded` action has not occurred yet, will postpone saving to the database.
*
* @since 6.4.0
*/
private function refresh_rewrite_rules() {
$this->rules = '';
$this->matches = 'matches';
$this->rewrite_rules();
if ( ! did_action( 'wp_loaded' ) ) {
/*
* Is not safe to save the results right now, as the rules may be partial.
* Need to give all rules the chance to register.
*/
add_action( 'wp_loaded', array( $this, 'flush_rules' ) );
} else {
update_option( 'rewrite_rules', $this->rules );
}
}
/**
* Retrieves mod_rewrite-formatted rewrite rules to write to .htaccess.
*
* Does not actually write to the .htaccess file, but creates the rules for
* the process that will.
*
* Will add the non_wp_rules property rules to the .htaccess file before
* the WordPress rewrite rules one.
*
* @since 1.5.0
*
* @return string
*/
public function mod_rewrite_rules() {
if ( ! $this->using_permalinks() ) {
return '';
}
$site_root = parse_url( site_url() );
if ( isset( $site_root['path'] ) ) {
$site_root = trailingslashit( $site_root['path'] );
}
$home_root = parse_url( home_url() );
if ( isset( $home_root['path'] ) ) {
$home_root = trailingslashit( $home_root['path'] );
} else {
$home_root = '/';
}
$rules = "\n";
$rules .= "RewriteEngine On\n";
$rules .= "RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]\n";
$rules .= "RewriteBase $home_root\n";
// Prevent -f checks on index.php.
$rules .= "RewriteRule ^index\.php$ - [L]\n";
// Add in the rules that don't redirect to WP's index.php (and thus shouldn't be handled by WP at all).
foreach ( (array) $this->non_wp_rules as $match => $query ) {
// Apache 1.3 does not support the reluctant (non-greedy) modifier.
$match = str_replace( '.+?', '.+', $match );
$rules .= 'RewriteRule ^' . $match . ' ' . $home_root . $query . " [QSA,L]\n";
}
if ( $this->use_verbose_rules ) {
$this->matches = '';
$rewrite = $this->rewrite_rules();
$num_rules = count( $rewrite );
$rules .= "RewriteCond %{REQUEST_FILENAME} -f [OR]\n" .
"RewriteCond %{REQUEST_FILENAME} -d\n" .
"RewriteRule ^.*$ - [S=$num_rules]\n";
foreach ( (array) $rewrite as $match => $query ) {
// Apache 1.3 does not support the reluctant (non-greedy) modifier.
$match = str_replace( '.+?', '.+', $match );
if ( str_contains( $query, $this->index ) ) {
$rules .= 'RewriteRule ^' . $match . ' ' . $home_root . $query . " [QSA,L]\n";
} else {
$rules .= 'RewriteRule ^' . $match . ' ' . $site_root . $query . " [QSA,L]\n";
}
}
} else {
$rules .= "RewriteCond %{REQUEST_FILENAME} !-f\n" .
"RewriteCond %{REQUEST_FILENAME} !-d\n" .
"RewriteRule . {$home_root}{$this->index} [L]\n";
}
$rules .= "\n";
/**
* Filters the list of rewrite rules formatted for output to an .htaccess file.
*
* @since 1.5.0
*
* @param string $rules mod_rewrite Rewrite rules formatted for .htaccess.
*/
$rules = apply_filters( 'mod_rewrite_rules', $rules );
/**
* Filters the list of rewrite rules formatted for output to an .htaccess file.
*
* @since 1.5.0
* @deprecated 1.5.0 Use the {@see 'mod_rewrite_rules'} filter instead.
*
* @param string $rules mod_rewrite Rewrite rules formatted for .htaccess.
*/
return apply_filters_deprecated( 'rewrite_rules', array( $rules ), '1.5.0', 'mod_rewrite_rules' );
}
/**
* Retrieves IIS7 URL Rewrite formatted rewrite rules to write to web.config file.
*
* Does not actually write to the web.config file, but creates the rules for
* the process that will.
*
* @since 2.8.0
*
* @param bool $add_parent_tags Optional. Whether to add parent tags to the rewrite rule sets.
* Default false.
* @return string IIS7 URL rewrite rule sets.
*/
public function iis7_url_rewrite_rules( $add_parent_tags = false ) {
if ( ! $this->using_permalinks() ) {
return '';
}
$rules = '';
if ( $add_parent_tags ) {
$rules .= '
';
}
$rules .= '
';
if ( $add_parent_tags ) {
$rules .= '
';
}
/**
* Filters the list of rewrite rules formatted for output to a web.config.
*
* @since 2.8.0
*
* @param string $rules Rewrite rules formatted for IIS web.config.
*/
return apply_filters( 'iis7_url_rewrite_rules', $rules );
}
/**
* Adds a rewrite rule that transforms a URL structure to a set of query vars.
*
* Any value in the $after parameter that isn't 'bottom' will result in the rule
* being placed at the top of the rewrite rules.
*
* @since 2.1.0
* @since 4.4.0 Array support was added to the `$query` parameter.
*
* @param string $regex Regular expression to match request against.
* @param string|array $query The corresponding query vars for this rewrite rule.
* @param string $after Optional. Priority of the new rule. Accepts 'top'
* or 'bottom'. Default 'bottom'.
*/
public function add_rule( $regex, $query, $after = 'bottom' ) {
if ( is_array( $query ) ) {
$external = false;
$query = add_query_arg( $query, 'index.php' );
} else {
$index = ! str_contains( $query, '?' ) ? strlen( $query ) : strpos( $query, '?' );
$front = substr( $query, 0, $index );
$external = $front !== $this->index;
}
// "external" = it doesn't correspond to index.php.
if ( $external ) {
$this->add_external_rule( $regex, $query );
} else {
if ( 'bottom' === $after ) {
$this->extra_rules = array_merge( $this->extra_rules, array( $regex => $query ) );
} else {
$this->extra_rules_top = array_merge( $this->extra_rules_top, array( $regex => $query ) );
}
}
}
/**
* Adds a rewrite rule that doesn't correspond to index.php.
*
* @since 2.1.0
*
* @param string $regex Regular expression to match request against.
* @param string $query The corresponding query vars for this rewrite rule.
*/
public function add_external_rule( $regex, $query ) {
$this->non_wp_rules[ $regex ] = $query;
}
/**
* Adds an endpoint, like /trackback/.
*
* @since 2.1.0
* @since 3.9.0 $query_var parameter added.
* @since 4.3.0 Added support for skipping query var registration by passing `false` to `$query_var`.
*
* @see add_rewrite_endpoint() for full documentation.
* @global WP $wp Current WordPress environment instance.
*
* @param string $name Name of the endpoint.
* @param int $places Endpoint mask describing the places the endpoint should be added.
* Accepts a mask of:
* - `EP_ALL`
* - `EP_NONE`
* - `EP_ALL_ARCHIVES`
* - `EP_ATTACHMENT`
* - `EP_AUTHORS`
* - `EP_CATEGORIES`
* - `EP_COMMENTS`
* - `EP_DATE`
* - `EP_DAY`
* - `EP_MONTH`
* - `EP_PAGES`
* - `EP_PERMALINK`
* - `EP_ROOT`
* - `EP_SEARCH`
* - `EP_TAGS`
* - `EP_YEAR`
* @param string|bool $query_var Optional. Name of the corresponding query variable. Pass `false` to
* skip registering a query_var for this endpoint. Defaults to the
* value of `$name`.
*/
public function add_endpoint( $name, $places, $query_var = true ) {
global $wp;
// For backward compatibility, if null has explicitly been passed as `$query_var`, assume `true`.
if ( true === $query_var || null === $query_var ) {
$query_var = $name;
}
$this->endpoints[] = array( $places, $name, $query_var );
if ( $query_var ) {
$wp->add_query_var( $query_var );
}
}
/**
* Adds a new permalink structure.
*
* A permalink structure (permastruct) is an abstract definition of a set of rewrite rules;
* it is an easy way of expressing a set of regular expressions that rewrite to a set of
* query strings. The new permastruct is added to the WP_Rewrite::$extra_permastructs array.
*
* When the rewrite rules are built by WP_Rewrite::rewrite_rules(), all of these extra
* permastructs are passed to WP_Rewrite::generate_rewrite_rules() which transforms them
* into the regular expressions that many love to hate.
*
* The `$args` parameter gives you control over how WP_Rewrite::generate_rewrite_rules()
* works on the new permastruct.
*
* @since 2.5.0
*
* @param string $name Name for permalink structure.
* @param string $struct Permalink structure (e.g. category/%category%)
* @param array $args {
* Optional. Arguments for building rewrite rules based on the permalink structure.
* Default empty array.
*
* @type bool $with_front Whether the structure should be prepended with `WP_Rewrite::$front`.
* Default true.
* @type int $ep_mask The endpoint mask defining which endpoints are added to the structure.
* Accepts a mask of:
* - `EP_ALL`
* - `EP_NONE`
* - `EP_ALL_ARCHIVES`
* - `EP_ATTACHMENT`
* - `EP_AUTHORS`
* - `EP_CATEGORIES`
* - `EP_COMMENTS`
* - `EP_DATE`
* - `EP_DAY`
* - `EP_MONTH`
* - `EP_PAGES`
* - `EP_PERMALINK`
* - `EP_ROOT`
* - `EP_SEARCH`
* - `EP_TAGS`
* - `EP_YEAR`
* Default `EP_NONE`.
* @type bool $paged Whether archive pagination rules should be added for the structure.
* Default true.
* @type bool $feed Whether feed rewrite rules should be added for the structure. Default true.
* @type bool $forcomments Whether the feed rules should be a query for a comments feed. Default false.
* @type bool $walk_dirs Whether the 'directories' making up the structure should be walked over
* and rewrite rules built for each in-turn. Default true.
* @type bool $endpoints Whether endpoints should be applied to the generated rules. Default true.
* }
*/
public function add_permastruct( $name, $struct, $args = array() ) {
// Back-compat for the old parameters: $with_front and $ep_mask.
if ( ! is_array( $args ) ) {
$args = array( 'with_front' => $args );
}
if ( func_num_args() === 4 ) {
$args['ep_mask'] = func_get_arg( 3 );
}
$defaults = array(
'with_front' => true,
'ep_mask' => EP_NONE,
'paged' => true,
'feed' => true,
'forcomments' => false,
'walk_dirs' => true,
'endpoints' => true,
);
$args = array_intersect_key( $args, $defaults );
$args = wp_parse_args( $args, $defaults );
if ( $args['with_front'] ) {
$struct = $this->front . $struct;
} else {
$struct = $this->root . $struct;
}
$args['struct'] = $struct;
$this->extra_permastructs[ $name ] = $args;
}
/**
* Removes a permalink structure.
*
* @since 4.5.0
*
* @param string $name Name for permalink structure.
*/
public function remove_permastruct( $name ) {
unset( $this->extra_permastructs[ $name ] );
}
/**
* Removes rewrite rules and then recreate rewrite rules.
*
* Calls WP_Rewrite::wp_rewrite_rules() after removing the 'rewrite_rules' option.
* If the function named 'save_mod_rewrite_rules' exists, it will be called.
*
* @since 2.0.1
*
* @param bool $hard Whether to update .htaccess (hard flush) or just update rewrite_rules option (soft flush). Default is true (hard).
*/
public function flush_rules( $hard = true ) {
static $do_hard_later = null;
// Prevent this action from running before everyone has registered their rewrites.
if ( ! did_action( 'wp_loaded' ) ) {
add_action( 'wp_loaded', array( $this, 'flush_rules' ) );
$do_hard_later = ( isset( $do_hard_later ) ) ? $do_hard_later || $hard : $hard;
return;
}
if ( isset( $do_hard_later ) ) {
$hard = $do_hard_later;
unset( $do_hard_later );
}
$this->refresh_rewrite_rules();
/**
* Filters whether a "hard" rewrite rule flush should be performed when requested.
*
* A "hard" flush updates .htaccess (Apache) or web.config (IIS).
*
* @since 3.7.0
*
* @param bool $hard Whether to flush rewrite rules "hard". Default true.
*/
if ( ! $hard || ! apply_filters( 'flush_rewrite_rules_hard', true ) ) {
return;
}
if ( function_exists( 'save_mod_rewrite_rules' ) ) {
save_mod_rewrite_rules();
}
if ( function_exists( 'iis7_save_url_rewrite_rules' ) ) {
iis7_save_url_rewrite_rules();
}
}
/**
* Sets up the object's properties.
*
* The 'use_verbose_page_rules' object property will be set to true if the
* permalink structure begins with one of the following: '%postname%', '%category%',
* '%tag%', or '%author%'.
*
* @since 1.5.0
*/
public function init() {
$this->extra_rules = array();
$this->non_wp_rules = array();
$this->endpoints = array();
$this->permalink_structure = get_option( 'permalink_structure' );
$this->front = substr( $this->permalink_structure, 0, strpos( $this->permalink_structure, '%' ) );
$this->root = '';
if ( $this->using_index_permalinks() ) {
$this->root = $this->index . '/';
}
unset( $this->author_structure );
unset( $this->date_structure );
unset( $this->page_structure );
unset( $this->search_structure );
unset( $this->feed_structure );
unset( $this->comment_feed_structure );
$this->use_trailing_slashes = str_ends_with( $this->permalink_structure, '/' );
// Enable generic rules for pages if permalink structure doesn't begin with a wildcard.
if ( preg_match( '/^[^%]*%(?:postname|category|tag|author)%/', $this->permalink_structure ) ) {
$this->use_verbose_page_rules = true;
} else {
$this->use_verbose_page_rules = false;
}
}
/**
* Sets the main permalink structure for the site.
*
* Will update the 'permalink_structure' option, if there is a difference
* between the current permalink structure and the parameter value. Calls
* WP_Rewrite::init() after the option is updated.
*
* Fires the {@see 'permalink_structure_changed'} action once the init call has
* processed passing the old and new values
*
* @since 1.5.0
*
* @param string $permalink_structure Permalink structure.
*/
public function set_permalink_structure( $permalink_structure ) {
if ( $this->permalink_structure !== $permalink_structure ) {
$old_permalink_structure = $this->permalink_structure;
update_option( 'permalink_structure', $permalink_structure );
$this->init();
/**
* Fires after the permalink structure is updated.
*
* @since 2.8.0
*
* @param string $old_permalink_structure The previous permalink structure.
* @param string $permalink_structure The new permalink structure.
*/
do_action( 'permalink_structure_changed', $old_permalink_structure, $permalink_structure );
}
}
/**
* Sets the category base for the category permalink.
*
* Will update the 'category_base' option, if there is a difference between
* the current category base and the parameter value. Calls WP_Rewrite::init()
* after the option is updated.
*
* @since 1.5.0
*
* @param string $category_base Category permalink structure base.
*/
public function set_category_base( $category_base ) {
if ( get_option( 'category_base' ) !== $category_base ) {
update_option( 'category_base', $category_base );
$this->init();
}
}
/**
* Sets the tag base for the tag permalink.
*
* Will update the 'tag_base' option, if there is a difference between the
* current tag base and the parameter value. Calls WP_Rewrite::init() after
* the option is updated.
*
* @since 2.3.0
*
* @param string $tag_base Tag permalink structure base.
*/
public function set_tag_base( $tag_base ) {
if ( get_option( 'tag_base' ) !== $tag_base ) {
update_option( 'tag_base', $tag_base );
$this->init();
}
}
/**
* Constructor - Calls init(), which runs setup.
*
* @since 1.5.0
*/
public function __construct() {
$this->init();
}
}
tion( __METHOD__, '1.24.0', 'Automattic\\Jetpack\\Connection\\Secrets->get' );
return ( new Secrets() )->get( $action, $user_id );
}
/**
* Deletes secret tokens in case they, for example, have expired.
*
* @deprecated 1.24.0 Use Automattic\Jetpack\Connection\Secrets->delete() instead.
*
* @param String $action The action name.
* @param Integer $user_id The user identifier.
*/
public function delete_secrets( $action, $user_id ) {
_deprecated_function( __METHOD__, '1.24.0', 'Automattic\\Jetpack\\Connection\\Secrets->delete' );
( new Secrets() )->delete( $action, $user_id );
}
/**
* Deletes all connection tokens and transients from the local Jetpack site.
* If the plugin object has been provided in the constructor, the function first checks
* whether it's the only active connection.
* If there are any other connections, the function will do nothing and return `false`
* (unless `$ignore_connected_plugins` is set to `true`).
*
* @param bool $ignore_connected_plugins Delete the tokens even if there are other connected plugins.
*
* @return bool True if disconnected successfully, false otherwise.
*/
public function delete_all_connection_tokens( $ignore_connected_plugins = false ) {
// refuse to delete if we're not the last Jetpack plugin installed.
if ( ! $ignore_connected_plugins && null !== $this->plugin && ! $this->plugin->is_only() ) {
return false;
}
/**
* Fires upon the disconnect attempt.
* Return `false` to prevent the disconnect.
*
* @since 1.14.2
*/
if ( ! apply_filters( 'jetpack_connection_delete_all_tokens', true ) ) {
return false;
}
\Jetpack_Options::delete_option(
array(
'master_user',
'time_diff',
'fallback_no_verify_ssl_certs',
)
);
( new Secrets() )->delete_all();
$this->get_tokens()->delete_all();
// Delete cached connected user data.
$transient_key = 'jetpack_connected_user_data_' . get_current_user_id();
delete_transient( $transient_key );
// Delete all XML-RPC errors.
Error_Handler::get_instance()->delete_all_errors();
return true;
}
/**
* Tells WordPress.com to disconnect the site and clear all tokens from cached site.
* If the plugin object has been provided in the constructor, the function first check
* whether it's the only active connection.
* If there are any other connections, the function will do nothing and return `false`
* (unless `$ignore_connected_plugins` is set to `true`).
*
* @param bool $ignore_connected_plugins Delete the tokens even if there are other connected plugins.
*
* @return bool True if disconnected successfully, false otherwise.
*/
public function disconnect_site_wpcom( $ignore_connected_plugins = false ) {
if ( ! $ignore_connected_plugins && null !== $this->plugin && ! $this->plugin->is_only() ) {
return false;
}
if ( ( new Status() )->is_offline_mode() && ! apply_filters( 'jetpack_connection_disconnect_site_wpcom_offline_mode', false ) ) {
// Prevent potential disconnect of the live site by removing WPCOM tokens.
return false;
}
/**
* Fires upon the disconnect attempt.
* Return `false` to prevent the disconnect.
*
* @since 1.14.2
*/
if ( ! apply_filters( 'jetpack_connection_disconnect_site_wpcom', true, $this ) ) {
return false;
}
$xml = new Jetpack_IXR_Client();
$xml->query( 'jetpack.deregister', get_current_user_id() );
return true;
}
/**
* Disconnect the plugin and remove the tokens.
* This function will automatically perform "soft" or "hard" disconnect depending on whether other plugins are using the connection.
* This is a proxy method to simplify the Connection package API.
*
* @see Manager::disconnect_site()
*
* @param boolean $disconnect_wpcom Should disconnect_site_wpcom be called.
* @param bool $ignore_connected_plugins Delete the tokens even if there are other connected plugins.
* @return bool
*/
public function remove_connection( $disconnect_wpcom = true, $ignore_connected_plugins = false ) {
$this->disconnect_site( $disconnect_wpcom, $ignore_connected_plugins );
return true;
}
/**
* Completely clearing up the connection, and initiating reconnect.
*
* @return true|WP_Error True if reconnected successfully, a `WP_Error` object otherwise.
*/
public function reconnect() {
( new Tracking() )->record_user_event( 'restore_connection_reconnect' );
$this->disconnect_site_wpcom( true );
return $this->register();
}
/**
* Validate the tokens, and refresh the invalid ones.
*
* @return string|bool|WP_Error True if connection restored or string indicating what's to be done next. A `WP_Error` object or false otherwise.
*/
public function restore() {
// If this is a site connection we need to trigger a full reconnection as our only secure means of
// communication with WPCOM, aka the blog token, is compromised.
if ( $this->is_site_connection() ) {
return $this->reconnect();
}
$validate_tokens_response = $this->get_tokens()->validate();
// If token validation failed, trigger a full reconnection.
if ( is_array( $validate_tokens_response ) &&
isset( $validate_tokens_response['blog_token']['is_healthy'] ) &&
isset( $validate_tokens_response['user_token']['is_healthy'] ) ) {
$blog_token_healthy = $validate_tokens_response['blog_token']['is_healthy'];
$user_token_healthy = $validate_tokens_response['user_token']['is_healthy'];
} else {
$blog_token_healthy = false;
$user_token_healthy = false;
}
// Tokens are both valid, or both invalid. We can't fix the problem we don't see, so the full reconnection is needed.
if ( $blog_token_healthy === $user_token_healthy ) {
$result = $this->reconnect();
return ( true === $result ) ? 'authorize' : $result;
}
if ( ! $blog_token_healthy ) {
return $this->refresh_blog_token();
}
if ( ! $user_token_healthy ) {
return ( true === $this->refresh_user_token() ) ? 'authorize' : false;
}
return false;
}
/**
* Responds to a WordPress.com call to register the current site.
* Should be changed to protected.
*
* @param array $registration_data Array of [ secret_1, user_id ].
*/
public function handle_registration( array $registration_data ) {
list( $registration_secret_1, $registration_user_id ) = $registration_data;
if ( empty( $registration_user_id ) ) {
return new \WP_Error( 'registration_state_invalid', __( 'Invalid Registration State', 'jetpack-connection' ), 400 );
}
return ( new Secrets() )->verify( 'register', $registration_secret_1, (int) $registration_user_id );
}
/**
* Perform the API request to validate the blog and user tokens.
*
* @deprecated 1.24.0 Use Automattic\Jetpack\Connection\Tokens->validate_tokens() instead.
*
* @param int|null $user_id ID of the user we need to validate token for. Current user's ID by default.
*
* @return array|false|WP_Error The API response: `array( 'blog_token_is_healthy' => true|false, 'user_token_is_healthy' => true|false )`.
*/
public function validate_tokens( $user_id = null ) {
_deprecated_function( __METHOD__, '1.24.0', 'Automattic\\Jetpack\\Connection\\Tokens->validate' );
return $this->get_tokens()->validate( $user_id );
}
/**
* Verify a Previously Generated Secret.
*
* @deprecated 1.24.0 Use Automattic\Jetpack\Connection\Secrets->verify() instead.
*
* @param string $action The type of secret to verify.
* @param string $secret_1 The secret string to compare to what is stored.
* @param int $user_id The user ID of the owner of the secret.
* @return \WP_Error|string WP_Error on failure, secret_2 on success.
*/
public function verify_secrets( $action, $secret_1, $user_id ) {
_deprecated_function( __METHOD__, '1.24.0', 'Automattic\\Jetpack\\Connection\\Secrets->verify' );
return ( new Secrets() )->verify( $action, $secret_1, $user_id );
}
/**
* Responds to a WordPress.com call to authorize the current user.
* Should be changed to protected.
*/
public function handle_authorization() {
}
/**
* Obtains the auth token.
*
* @param array $data The request data.
* @return object|\WP_Error Returns the auth token on success.
* Returns a \WP_Error on failure.
*/
public function get_token( $data ) {
return $this->get_tokens()->get( $data, $this->api_url( 'token' ) );
}
/**
* Builds a URL to the Jetpack connection auth page.
*
* @param WP_User $user (optional) defaults to the current logged in user.
* @param String $redirect (optional) a redirect URL to use instead of the default.
* @return string Connect URL.
*/
public function get_authorization_url( $user = null, $redirect = null ) {
if ( empty( $user ) ) {
$user = wp_get_current_user();
}
$roles = new Roles();
$role = $roles->translate_user_to_role( $user );
$signed_role = $this->get_tokens()->sign_role( $role );
/**
* Filter the URL of the first time the user gets redirected back to your site for connection
* data processing.
*
* @since 1.7.0
* @since-jetpack 8.0.0
*
* @param string $redirect_url Defaults to the site admin URL.
*/
$processing_url = apply_filters( 'jetpack_connect_processing_url', admin_url( 'admin.php' ) );
/**
* Filter the URL to redirect the user back to when the authorization process
* is complete.
*
* @since 1.7.0
* @since-jetpack 8.0.0
*
* @param string $redirect_url Defaults to the site URL.
*/
$redirect = apply_filters( 'jetpack_connect_redirect_url', $redirect );
$secrets = ( new Secrets() )->generate( 'authorize', $user->ID, 2 * HOUR_IN_SECONDS );
/**
* Filter the type of authorization.
* 'calypso' completes authorization on wordpress.com/jetpack/connect
* while 'jetpack' ( or any other value ) completes the authorization at jetpack.wordpress.com.
*
* @since 1.7.0
* @since-jetpack 4.3.3
*
* @param string $auth_type Defaults to 'calypso', can also be 'jetpack'.
*/
$auth_type = apply_filters( 'jetpack_auth_type', 'calypso' );
/**
* Filters the user connection request data for additional property addition.
*
* @since 1.7.0
* @since-jetpack 8.0.0
*
* @param array $request_data request data.
*/
$body = apply_filters(
'jetpack_connect_request_body',
array(
'response_type' => 'code',
'client_id' => \Jetpack_Options::get_option( 'id' ),
'redirect_uri' => add_query_arg(
array(
'handler' => 'jetpack-connection-webhooks',
'action' => 'authorize',
'_wpnonce' => wp_create_nonce( "jetpack-authorize_{$role}_{$redirect}" ),
'redirect' => $redirect ? rawurlencode( $redirect ) : false,
),
esc_url( $processing_url )
),
'state' => $user->ID,
'scope' => $signed_role,
'user_email' => $user->user_email,
'user_login' => $user->user_login,
'is_active' => $this->has_connected_owner(), // TODO Deprecate this.
'jp_version' => (string) Constants::get_constant( 'JETPACK__VERSION' ),
'auth_type' => $auth_type,
'secret' => $secrets['secret_1'],
'blogname' => get_option( 'blogname' ),
'site_url' => Urls::site_url(),
'home_url' => Urls::home_url(),
'site_icon' => get_site_icon_url(),
'site_lang' => get_locale(),
'site_created' => $this->get_assumed_site_creation_date(),
'allow_site_connection' => ! $this->has_connected_owner(),
'calypso_env' => ( new Host() )->get_calypso_env(),
'source' => ( new Host() )->get_source_query(),
)
);
$body = static::apply_activation_source_to_args( urlencode_deep( $body ) );
$api_url = $this->api_url( 'authorize' );
$url = add_query_arg( $body, $api_url );
/** This filter is documented in plugins/jetpack/class-jetpack.php */
return apply_filters( 'jetpack_build_authorize_url', $url );
}
/**
* Authorizes the user by obtaining and storing the user token.
*
* @param array $data The request data.
* @return string|\WP_Error Returns a string on success.
* Returns a \WP_Error on failure.
*/
public function authorize( $data = array() ) {
/**
* Action fired when user authorization starts.
*
* @since 1.7.0
* @since-jetpack 8.0.0
*/
do_action( 'jetpack_authorize_starting' );
$roles = new Roles();
$role = $roles->translate_current_user_to_role();
if ( ! $role ) {
return new \WP_Error( 'no_role', 'Invalid request.', 400 );
}
$cap = $roles->translate_role_to_cap( $role );
if ( ! $cap ) {
return new \WP_Error( 'no_cap', 'Invalid request.', 400 );
}
if ( ! empty( $data['error'] ) ) {
return new \WP_Error( $data['error'], 'Error included in the request.', 400 );
}
if ( ! isset( $data['state'] ) ) {
return new \WP_Error( 'no_state', 'Request must include state.', 400 );
}
if ( ! ctype_digit( $data['state'] ) ) {
return new \WP_Error( $data['error'], 'State must be an integer.', 400 );
}
$current_user_id = get_current_user_id();
if ( $current_user_id !== (int) $data['state'] ) {
return new \WP_Error( 'wrong_state', 'State does not match current user.', 400 );
}
if ( empty( $data['code'] ) ) {
return new \WP_Error( 'no_code', 'Request must include an authorization code.', 400 );
}
$token = $this->get_tokens()->get( $data, $this->api_url( 'token' ) );
if ( is_wp_error( $token ) ) {
$code = $token->get_error_code();
if ( empty( $code ) ) {
$code = 'invalid_token';
}
return new \WP_Error( $code, $token->get_error_message(), 400 );
}
if ( ! $token ) {
return new \WP_Error( 'no_token', 'Error generating token.', 400 );
}
$is_connection_owner = ! $this->has_connected_owner();
$this->get_tokens()->update_user_token( $current_user_id, sprintf( '%s.%d', $token, $current_user_id ), $is_connection_owner );
/**
* Fires after user has successfully received an auth token.
*
* @since 1.7.0
* @since-jetpack 3.9.0
*/
do_action( 'jetpack_user_authorized' );
if ( ! $is_connection_owner ) {
/**
* Action fired when a secondary user has been authorized.
*
* @since 1.7.0
* @since-jetpack 8.0.0
*/
do_action( 'jetpack_authorize_ending_linked' );
return 'linked';
}
/**
* Action fired when the master user has been authorized.
*
* @since 1.7.0
* @since-jetpack 8.0.0
*
* @param array $data The request data.
*/
do_action( 'jetpack_authorize_ending_authorized', $data );
\Jetpack_Options::delete_raw_option( 'jetpack_last_connect_url_check' );
( new Nonce_Handler() )->reschedule();
return 'authorized';
}
/**
* Disconnects from the Jetpack servers.
* Forgets all connection details and tells the Jetpack servers to do the same.
*
* @param boolean $disconnect_wpcom Should disconnect_site_wpcom be called.
* @param bool $ignore_connected_plugins Delete the tokens even if there are other connected plugins.
*/
public function disconnect_site( $disconnect_wpcom = true, $ignore_connected_plugins = true ) {
if ( ! $ignore_connected_plugins && null !== $this->plugin && ! $this->plugin->is_only() ) {
return false;
}
wp_clear_scheduled_hook( 'jetpack_clean_nonces' );
( new Nonce_Handler() )->clean_all();
/**
* Fires when a site is disconnected.
*
* @since 1.36.3
*/
do_action( 'jetpack_site_before_disconnected' );
// If the site is in an IDC because sync is not allowed,
// let's make sure to not disconnect the production site.
if ( $disconnect_wpcom ) {
$tracking = new Tracking();
$tracking->record_user_event( 'disconnect_site', array() );
$this->disconnect_site_wpcom( $ignore_connected_plugins );
}
$this->delete_all_connection_tokens( $ignore_connected_plugins );
// Remove tracked package versions, since they depend on the Jetpack Connection.
delete_option( Package_Version_Tracker::PACKAGE_VERSION_OPTION );
$jetpack_unique_connection = \Jetpack_Options::get_option( 'unique_connection' );
if ( $jetpack_unique_connection ) {
// Check then record unique disconnection if site has never been disconnected previously.
if ( - 1 === $jetpack_unique_connection['disconnected'] ) {
$jetpack_unique_connection['disconnected'] = 1;
} else {
if ( 0 === $jetpack_unique_connection['disconnected'] ) {
$a8c_mc_stats_instance = new A8c_Mc_Stats();
$a8c_mc_stats_instance->add( 'connections', 'unique-disconnect' );
$a8c_mc_stats_instance->do_server_side_stats();
}
// increment number of times disconnected.
$jetpack_unique_connection['disconnected'] += 1;
}
\Jetpack_Options::update_option( 'unique_connection', $jetpack_unique_connection );
}
/**
* Fires when a site is disconnected.
*
* @since 1.30.1
*/
do_action( 'jetpack_site_disconnected' );
}
/**
* The Base64 Encoding of the SHA1 Hash of the Input.
*
* @param string $text The string to hash.
* @return string
*/
public function sha1_base64( $text ) {
return base64_encode( sha1( $text, true ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
}
/**
* This function mirrors Jetpack_Data::is_usable_domain() in the WPCOM codebase.
*
* @param string $domain The domain to check.
*
* @return bool|WP_Error
*/
public function is_usable_domain( $domain ) {
// If it's empty, just fail out.
if ( ! $domain ) {
return new \WP_Error(
'fail_domain_empty',
/* translators: %1$s is a domain name. */
sprintf( __( 'Domain `%1$s` just failed is_usable_domain check as it is empty.', 'jetpack-connection' ), $domain )
);
}
/**
* Skips the usuable domain check when connecting a site.
*
* Allows site administrators with domains that fail gethostname-based checks to pass the request to WP.com
*
* @since 1.7.0
* @since-jetpack 4.1.0
*
* @param bool If the check should be skipped. Default false.
*/
if ( apply_filters( 'jetpack_skip_usuable_domain_check', false ) ) {
return true;
}
// None of the explicit localhosts.
$forbidden_domains = array(
'wordpress.com',
'localhost',
'localhost.localdomain',
'local.wordpress.test', // VVV pattern.
'local.wordpress-trunk.test', // VVV pattern.
'src.wordpress-develop.test', // VVV pattern.
'build.wordpress-develop.test', // VVV pattern.
);
if ( in_array( $domain, $forbidden_domains, true ) ) {
return new \WP_Error(
'fail_domain_forbidden',
sprintf(
/* translators: %1$s is a domain name. */
__(
'Domain `%1$s` just failed is_usable_domain check as it is in the forbidden array.',
'jetpack-connection'
),
$domain
)
);
}
// No .test or .local domains.
if ( preg_match( '#\.(test|local)$#i', $domain ) ) {
return new \WP_Error(
'fail_domain_tld',
sprintf(
/* translators: %1$s is a domain name. */
__(
'Domain `%1$s` just failed is_usable_domain check as it uses an invalid top level domain.',
'jetpack-connection'
),
$domain
)
);
}
// No WPCOM subdomains.
if ( preg_match( '#\.WordPress\.com$#i', $domain ) ) {
return new \WP_Error(
'fail_subdomain_wpcom',
sprintf(
/* translators: %1$s is a domain name. */
__(
'Domain `%1$s` just failed is_usable_domain check as it is a subdomain of WordPress.com.',
'jetpack-connection'
),
$domain
)
);
}
// If PHP was compiled without support for the Filter module (very edge case).
if ( ! function_exists( 'filter_var' ) ) {
// Just pass back true for now, and let wpcom sort it out.
return true;
}
$domain = preg_replace( '#^https?://#', '', untrailingslashit( $domain ) );
if ( filter_var( $domain, FILTER_VALIDATE_IP )
&& ! filter_var( $domain, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE )
) {
return new \WP_Error(
'fail_ip_forbidden',
sprintf(
/* translators: %1$s is a domain name. */
__(
'IP address `%1$s` just failed is_usable_domain check as it is in the private network.',
'jetpack-connection'
),
$domain
)
);
}
return true;
}
/**
* Gets the requested token.
*
* @deprecated 1.24.0 Use Automattic\Jetpack\Connection\Tokens->get_access_token() instead.
*
* @param int|false $user_id false: Return the Blog Token. int: Return that user's User Token.
* @param string|false $token_key If provided, check that the token matches the provided input.
* @param bool|true $suppress_errors If true, return a falsy value when the token isn't found; When false, return a descriptive WP_Error when the token isn't found.
*
* @return object|false
*
* @see $this->get_tokens()->get_access_token()
*/
public function get_access_token( $user_id = false, $token_key = false, $suppress_errors = true ) {
_deprecated_function( __METHOD__, '1.24.0', 'Automattic\\Jetpack\\Connection\\Tokens->get_access_token' );
return $this->get_tokens()->get_access_token( $user_id, $token_key, $suppress_errors );
}
/**
* In some setups, $HTTP_RAW_POST_DATA can be emptied during some IXR_Server paths
* since it is passed by reference to various methods.
* Capture it here so we can verify the signature later.
*
* @param array $methods an array of available XMLRPC methods.
* @return array the same array, since this method doesn't add or remove anything.
*/
public function xmlrpc_methods( $methods ) {
$this->raw_post_data = isset( $GLOBALS['HTTP_RAW_POST_DATA'] ) ? $GLOBALS['HTTP_RAW_POST_DATA'] : null;
return $methods;
}
/**
* Resets the raw post data parameter for testing purposes.
*/
public function reset_raw_post_data() {
$this->raw_post_data = null;
}
/**
* Registering an additional method.
*
* @param array $methods an array of available XMLRPC methods.
* @return array the amended array in case the method is added.
*/
public function public_xmlrpc_methods( $methods ) {
if ( array_key_exists( 'wp.getOptions', $methods ) ) {
$methods['wp.getOptions'] = array( $this, 'jetpack_get_options' );
}
return $methods;
}
/**
* Handles a getOptions XMLRPC method call.
*
* @param array $args method call arguments.
* @return an amended XMLRPC server options array.
*/
public function jetpack_get_options( $args ) {
global $wp_xmlrpc_server;
$wp_xmlrpc_server->escape( $args );
$username = $args[1];
$password = $args[2];
$user = $wp_xmlrpc_server->login( $username, $password );
if ( ! $user ) {
return $wp_xmlrpc_server->error;
}
$options = array();
$user_data = $this->get_connected_user_data();
if ( is_array( $user_data ) ) {
$options['jetpack_user_id'] = array(
'desc' => __( 'The WP.com user ID of the connected user', 'jetpack-connection' ),
'readonly' => true,
'value' => $user_data['ID'],
);
$options['jetpack_user_login'] = array(
'desc' => __( 'The WP.com username of the connected user', 'jetpack-connection' ),
'readonly' => true,
'value' => $user_data['login'],
);
$options['jetpack_user_email'] = array(
'desc' => __( 'The WP.com user email of the connected user', 'jetpack-connection' ),
'readonly' => true,
'value' => $user_data['email'],
);
$options['jetpack_user_site_count'] = array(
'desc' => __( 'The number of sites of the connected WP.com user', 'jetpack-connection' ),
'readonly' => true,
'value' => $user_data['site_count'],
);
}
$wp_xmlrpc_server->blog_options = array_merge( $wp_xmlrpc_server->blog_options, $options );
$args = stripslashes_deep( $args );
return $wp_xmlrpc_server->wp_getOptions( $args );
}
/**
* Adds Jetpack-specific options to the output of the XMLRPC options method.
*
* @param array $options standard Core options.
* @return array amended options.
*/
public function xmlrpc_options( $options ) {
$jetpack_client_id = false;
if ( $this->is_connected() ) {
$jetpack_client_id = \Jetpack_Options::get_option( 'id' );
}
$options['jetpack_version'] = array(
'desc' => __( 'Jetpack Plugin Version', 'jetpack-connection' ),
'readonly' => true,
'value' => Constants::get_constant( 'JETPACK__VERSION' ),
);
$options['jetpack_client_id'] = array(
'desc' => __( 'The Client ID/WP.com Blog ID of this site', 'jetpack-connection' ),
'readonly' => true,
'value' => $jetpack_client_id,
);
return $options;
}
/**
* Resets the saved authentication state in between testing requests.
*/
public function reset_saved_auth_state() {
$this->xmlrpc_verification = null;
}
/**
* Sign a user role with the master access token.
* If not specified, will default to the current user.
*
* @access public
*
* @param string $role User role.
* @param int $user_id ID of the user.
* @return string Signed user role.
*/
public function sign_role( $role, $user_id = null ) {
return $this->get_tokens()->sign_role( $role, $user_id );
}
/**
* Set the plugin instance.
*
* @param Plugin $plugin_instance The plugin instance.
*
* @return $this
*/
public function set_plugin_instance( Plugin $plugin_instance ) {
$this->plugin = $plugin_instance;
return $this;
}
/**
* Retrieve the plugin management object.
*
* @return Plugin|null
*/
public function get_plugin() {
return $this->plugin;
}
/**
* Get all connected plugins information, excluding those disconnected by user.
* WARNING: the method cannot be called until Plugin_Storage::configure is called, which happens on plugins_loaded
* Even if you don't use Jetpack Config, it may be introduced later by other plugins,
* so please make sure not to run the method too early in the code.
*
* @return array|WP_Error
*/
public function get_connected_plugins() {
$maybe_plugins = Plugin_Storage::get_all();
if ( $maybe_plugins instanceof WP_Error ) {
return $maybe_plugins;
}
return $maybe_plugins;
}
/**
* Force plugin disconnect. After its called, the plugin will not be allowed to use the connection.
* Note: this method does not remove any access tokens.
*
* @deprecated since 1.39.0
* @return bool
*/
public function disable_plugin() {
return null;
}
/**
* Force plugin reconnect after user-initiated disconnect.
* After its called, the plugin will be allowed to use the connection again.
* Note: this method does not initialize access tokens.
*
* @deprecated since 1.39.0.
* @return bool
*/
public function enable_plugin() {
return null;
}
/**
* Whether the plugin is allowed to use the connection, or it's been disconnected by user.
* If no plugin slug was passed into the constructor, always returns true.
*
* @deprecated 1.42.0 This method no longer has a purpose after the removal of the soft disconnect feature.
*
* @return bool
*/
public function is_plugin_enabled() {
return true;
}
/**
* Perform the API request to refresh the blog token.
* Note that we are making this request on behalf of the Jetpack master user,
* given they were (most probably) the ones that registered the site at the first place.
*
* @return WP_Error|bool The result of updating the blog_token option.
*/
public function refresh_blog_token() {
( new Tracking() )->record_user_event( 'restore_connection_refresh_blog_token' );
$blog_id = \Jetpack_Options::get_option( 'id' );
if ( ! $blog_id ) {
return new WP_Error( 'site_not_registered', 'Site not registered.' );
}
$url = sprintf(
'%s/%s/v%s/%s',
Constants::get_constant( 'JETPACK__WPCOM_JSON_API_BASE' ),
'wpcom',
'2',
'sites/' . $blog_id . '/jetpack-refresh-blog-token'
);
$method = 'POST';
$user_id = get_current_user_id();
$response = Client::remote_request( compact( 'url', 'method', 'user_id' ) );
if ( is_wp_error( $response ) ) {
return new WP_Error( 'refresh_blog_token_http_request_failed', $response->get_error_message() );
}
$code = wp_remote_retrieve_response_code( $response );
$entity = wp_remote_retrieve_body( $response );
if ( $entity ) {
$json = json_decode( $entity );
} else {
$json = false;
}
if ( 200 !== $code ) {
if ( empty( $json->code ) ) {
return new WP_Error( 'unknown', '', $code );
}
/* translators: Error description string. */
$error_description = isset( $json->message ) ? sprintf( __( 'Error Details: %s', 'jetpack-connection' ), (string) $json->message ) : '';
return new WP_Error( (string) $json->code, $error_description, $code );
}
if ( empty( $json->jetpack_secret ) || ! is_scalar( $json->jetpack_secret ) ) {
return new WP_Error( 'jetpack_secret', '', $code );
}
Error_Handler::get_instance()->delete_all_errors();
return $this->get_tokens()->update_blog_token( (string) $json->jetpack_secret );
}
/**
* Disconnect the user from WP.com, and initiate the reconnect process.
*
* @return bool
*/
public function refresh_user_token() {
( new Tracking() )->record_user_event( 'restore_connection_refresh_user_token' );
$this->disconnect_user( null, true, true );
return true;
}
/**
* Fetches a signed token.
*
* @deprecated 1.24.0 Use Automattic\Jetpack\Connection\Tokens->get_signed_token() instead.
*
* @param object $token the token.
* @return WP_Error|string a signed token
*/
public function get_signed_token( $token ) {
_deprecated_function( __METHOD__, '1.24.0', 'Automattic\\Jetpack\\Connection\\Tokens->get_signed_token' );
return $this->get_tokens()->get_signed_token( $token );
}
/**
* If the site-level connection is active, add the list of plugins using connection to the heartbeat (except Jetpack itself)
*
* @param array $stats The Heartbeat stats array.
* @return array $stats
*/
public function add_stats_to_heartbeat( $stats ) {
if ( ! $this->is_connected() ) {
return $stats;
}
$active_plugins_using_connection = Plugin_Storage::get_all();
foreach ( array_keys( $active_plugins_using_connection ) as $plugin_slug ) {
if ( 'jetpack' !== $plugin_slug ) {
$stats_group = isset( $active_plugins_using_connection['jetpack'] ) ? 'combined-connection' : 'standalone-connection';
$stats[ $stats_group ][] = $plugin_slug;
}
}
return $stats;
}
/**
* Get the WPCOM or self-hosted site ID.
*
* @return int|WP_Error
*/
public static function get_site_id() {
$is_wpcom = ( defined( 'IS_WPCOM' ) && IS_WPCOM );
$site_id = $is_wpcom ? get_current_blog_id() : \Jetpack_Options::get_option( 'id' );
if ( ! $site_id ) {
return new \WP_Error(
'unavailable_site_id',
__( 'Sorry, something is wrong with your Jetpack connection.', 'jetpack-connection' ),
403
);
}
return (int) $site_id;
}
/**
* Check if Jetpack is ready for uninstall cleanup.
*
* @param string $current_plugin_slug The current plugin's slug.
*
* @return bool
*/
public static function is_ready_for_cleanup( $current_plugin_slug ) {
$active_plugins = get_option( Plugin_Storage::ACTIVE_PLUGINS_OPTION_NAME );
return empty( $active_plugins ) || ! is_array( $active_plugins )
|| ( count( $active_plugins ) === 1 && array_key_exists( $current_plugin_slug, $active_plugins ) );
}
}