openInNewTabDefault = $openInNewTabDefault; $this->addNofollow = $addNofollow; $this->addSponsored = $addSponsored; $this->addNoopener = $addNoopener; $this->addNoreferrer = $addNoreferrer; $this->trackingEnabledDefault = $trackingEnabledDefault; } /** * Construct a link-defaults snapshot with the canonical * §13.6 baseline values: every flag enabled. */ public static function defaults(): self { return new self( true, true, true, true, true, true ); } /** * Default value of the "Open in New Tab" toggle. */ public function openInNewTabDefault(): bool { return $this->openInNewTabDefault; } /** * Whether `nofollow` is appended to the rel attribute. */ public function addNofollow(): bool { return $this->addNofollow; } /** * Whether `sponsored` is appended to the rel attribute. */ public function addSponsored(): bool { return $this->addSponsored; } /** * Whether `noopener` is appended to the rel attribute. */ public function addNoopener(): bool { return $this->addNoopener; } /** * Whether `noreferrer` is appended to the rel attribute. */ public function addNoreferrer(): bool { return $this->addNoreferrer; } /** * Default value of the "Tracking Enabled" toggle. */ public function trackingEnabledDefault(): bool { return $this->trackingEnabledDefault; } /** * Produce a copy with `openInNewTabDefault` replaced. * * @param bool $openInNewTabDefault New toggle. */ public function withOpenInNewTabDefault( bool $openInNewTabDefault ): self { $clone = clone $this; $clone->openInNewTabDefault = $openInNewTabDefault; return $clone; } /** * Produce a copy with `addNofollow` replaced. * * @param bool $addNofollow New flag. */ public function withAddNofollow( bool $addNofollow ): self { $clone = clone $this; $clone->addNofollow = $addNofollow; return $clone; } /** * Produce a copy with `addSponsored` replaced. * * @param bool $addSponsored New flag. */ public function withAddSponsored( bool $addSponsored ): self { $clone = clone $this; $clone->addSponsored = $addSponsored; return $clone; } /** * Produce a copy with `addNoopener` replaced. * * @param bool $addNoopener New flag. */ public function withAddNoopener( bool $addNoopener ): self { $clone = clone $this; $clone->addNoopener = $addNoopener; return $clone; } /** * Produce a copy with `addNoreferrer` replaced. * * @param bool $addNoreferrer New flag. */ public function withAddNoreferrer( bool $addNoreferrer ): self { $clone = clone $this; $clone->addNoreferrer = $addNoreferrer; return $clone; } /** * Produce a copy with `trackingEnabledDefault` replaced. * * @param bool $trackingEnabledDefault New toggle. */ public function withTrackingEnabledDefault( bool $trackingEnabledDefault ): self { $clone = clone $this; $clone->trackingEnabledDefault = $trackingEnabledDefault; return $clone; } /** * Report whether this snapshot equals the supplied one. * * Equality is structural: every flag must match under * strict comparison. * * @param self $other Other snapshot to compare against. */ public function equals( self $other ): bool { return $this->openInNewTabDefault === $other->openInNewTabDefault && $this->addNofollow === $other->addNofollow && $this->addSponsored === $other->addSponsored && $this->addNoopener === $other->addNoopener && $this->addNoreferrer === $other->addNoreferrer && $this->trackingEnabledDefault === $other->trackingEnabledDefault; } /** * Serialize this snapshot for OptionStore persistence. * * @return array */ public function toArray(): array { return array( 'open_in_new_tab_default' => $this->openInNewTabDefault, 'add_nofollow' => $this->addNofollow, 'add_sponsored' => $this->addSponsored, 'add_noopener' => $this->addNoopener, 'add_noreferrer' => $this->addNoreferrer, 'tracking_enabled_default' => $this->trackingEnabledDefault, ); } /** * Construct a snapshot from a {@see self::toArray()} payload. * * @param array $data Persisted payload. * * @throws InvalidArgumentException When `$data` is malformed. */ public static function fromArray( array $data ): self { return new self( self::requireBool( $data, 'open_in_new_tab_default' ), self::requireBool( $data, 'add_nofollow' ), self::requireBool( $data, 'add_sponsored' ), self::requireBool( $data, 'add_noopener' ), self::requireBool( $data, 'add_noreferrer' ), self::requireBool( $data, 'tracking_enabled_default' ) ); } /** * Read a required bool field from a {@see self::fromArray()} * payload. * * @param array $data Persisted payload. * @param string $key Field name. * * @throws InvalidArgumentException When the field is missing * or not a bool. */ private static function requireBool( array $data, string $key ): bool { if ( ! array_key_exists( $key, $data ) || ! is_bool( $data[ $key ] ) ) { $message = sprintf( 'LinkDefaults payload must carry a bool `%s`.', $key ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Developer-facing message, never rendered to HTML. throw new InvalidArgumentException( $message ); } return $data[ $key ]; } }