# jetpack/16.3/jetpack_vendor/automattic/jetpack-connection/src/class-protected-owner.php

Jetpack – WP Security, Backup, Speed, &amp; Growth, version 16.3. 156 lines.

- Page: https://pluginprobe.com/plugins/jetpack/16.3/code/jetpack_vendor/automattic/jetpack-connection/src/class-protected-owner.php
- Raw: https://pluginprobe.com/plugins/jetpack/16.3/raw/jetpack_vendor/automattic/jetpack-connection/src/class-protected-owner.php
- Modified: 2026-09-29T02:50:08+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/jetpack/16.3/code/jetpack_vendor/automattic/jetpack-connection/src/class-protected-owner.php#L10-L20`.

```php
<?php
/**
 * The Jetpack Connection Protected Owner class file.
 *
 * @package automattic/jetpack-connection
 */

namespace Automattic\Jetpack\Connection;

use Jetpack_Options;

/**
 * The local anchor naming the connection's protected owner.
 *
 * WordPress.com is authoritative on who the owner is; this records who the site was told to
 * expect, so ownership stops following whoever connected first. Identity is always matched on
 * `wpcom_user_id` — `local_user_id` is a re-pointable cache, never the match key.
 *
 * @since 9.3.0
 */
class Protected_Owner {

	const OPTION = 'protected_owner';

	/**
	 * Get the anchor.
	 *
	 * @since 9.3.0
	 *
	 * @return array|null The anchor, or null when none is usable.
	 */
	public static function get() {
		$anchor = Jetpack_Options::get_option( self::OPTION );

		if ( ! is_array( $anchor ) || empty( $anchor['wpcom_user_id'] ) ) {
			return null;
		}

		return $anchor;
	}

	/**
	 * Record the owner WordPress.com has confirmed for this site.
	 *
	 * @since 9.3.0
	 * @since 9.6.0 No longer records how the owner was confirmed.
	 *
	 * @param int $wpcom_user_id The owner's WordPress.com user ID, as confirmed by WordPress.com.
	 * @param int $local_user_id The owner's local WordPress user ID. Required here, though the
	 *                           anchor treats it as a re-pointable cache rather than the match
	 *                           key, so a caller that legitimately does not know it yet would
	 *                           need this relaxed.
	 * @return bool Whether the anchor is now stored as requested.
	 */
	public static function set( $wpcom_user_id, $local_user_id ) {
		$wpcom_user_id = absint( $wpcom_user_id );
		$local_user_id = absint( $local_user_id );

		// A zero ID would store an anchor `get()` rejects.
		if ( ! $wpcom_user_id || ! $local_user_id ) {
			return false;
		}

		$anchor = array(
			'wpcom_user_id' => $wpcom_user_id,
			'local_user_id' => $local_user_id,
			'confirmed_at'  => gmdate( 'Y-m-d\TH:i:s\Z' ),
		);

		if ( Jetpack_Options::update_option( self::OPTION, $anchor ) ) {
			return true;
		}

		// `update_option()` reports false for an unchanged value as well as for a failed write.
		return Jetpack_Options::get_option( self::OPTION ) === $anchor;
	}

	/**
	 * Point the anchor's cached local user ID at a different local user.
	 *
	 * The match key is `wpcom_user_id`; `local_user_id` is a cache of where that identity lives on
	 * this site, and it legitimately moves when the owner reconnects under another local account.
	 * Deliberately narrow: `confirmed_at` records when the owner was originally confirmed, and
	 * re-pointing a cache is not a new confirmation.
	 *
	 * @since 9.5.0
	 *
	 * @param int $local_user_id The local user the anchored identity now holds.
	 * @return bool Whether the anchor now names that local user.
	 */
	public static function repoint( $local_user_id ) {
		$local_user_id = absint( $local_user_id );
		$anchor        = self::get();

		if ( ! $anchor || ! $local_user_id ) {
			return false;
		}

		// Defaulted: `get()` only requires `wpcom_user_id`, so a partial anchor reaches here.
		if ( (int) ( $anchor['local_user_id'] ?? 0 ) === $local_user_id ) {
			return true;
		}

		$anchor['local_user_id'] = $local_user_id;

		if ( Jetpack_Options::update_option( self::OPTION, $anchor ) ) {
			return true;
		}

		return Jetpack_Options::get_option( self::OPTION ) === $anchor;
	}

	/**
	 * Drop the anchor, unlocking ownership.
	 *
	 * Leaves `master_user` alone: clearing the lock does not change who the owner is.
	 *
	 * @internal Recovery and support flows only. Consumers must not call this.
	 * @since 9.3.0
	 *
	 * @return bool Whether the anchor was deleted.
	 */
	public static function clear() {
		return Jetpack_Options::delete_option( self::OPTION );
	}

	/**
	 * Get the anchor, but only while it protects somebody.
	 *
	 * The anchor is dropped the moment WordPress.com stops confirming it, so holding one and
	 * being protected by it are the same thing. Kept as the name gates read by, which says what
	 * the call site means rather than what the storage happens to be.
	 *
	 * @since 9.3.0
	 *
	 * @return array|null The anchor, or null when there is none.
	 */
	public static function get_locked() {
		return self::get();
	}

	/**
	 * Whether an anchor is set and locked.
	 *
	 * Deliberately independent of whether the current owner matches it: a mismatch is when
	 * ownership most needs to stay locked.
	 *
	 * @since 9.3.0
	 *
	 * @return bool
	 */
	public static function is_locked() {
		return null !== self::get_locked();
	}
}

```
