|null The product and its variations, or null for every product (the catalog read). */ private $ids; /** * @param array|null $ids The product and its variations, or null for every product. */ private function __construct( ?array $ids ) { $this->ids = $ids; } /** * Calls $write with no current user and the scope above, then puts the previous user back. * * @template T * @param callable(): T $write The product's write. * @return T */ public static function run( int $product_id, callable $write ) { return ( new self( self::with_variations( $product_id ) ) )->as_nobody( $write ); } /** * Calls $read with no current user and read_private_products on every product, then puts the previous user back. * For the catalog push, which reads every product through wc/v3 and writes nothing. * * @template T * @param callable(): T $read The catalog push. * @return T */ public static function run_catalog_read( callable $read ) { return ( new self( null ) )->as_nobody( $read ); } /** * @template T * @param callable(): T $work What runs in this scope. * @return T */ private function as_nobody( callable $work ) { $previous = get_current_user_id(); $filter = array( $this, 'filter' ); // Action Scheduler may run inside an admin's page load: the work must not inherit that user. wp_set_current_user( 0 ); add_filter( 'user_has_cap', $filter, PHP_INT_MAX, 4 ); try { return $work(); } finally { remove_filter( 'user_has_cap', $filter, PHP_INT_MAX ); wp_set_current_user( $previous ); } } /** * user_has_cap, for the nobody user only: a check about any real user (such as whether the key's owner may still * manage WooCommerce) is left alone. * * Typed loosely on purpose: other plugins' user_has_cap filters run first and may pass anything on. * * @param mixed $allcaps What the user has. * @param mixed $caps The primitive capabilities being checked. * @param mixed $args The requested capability, the user id, then the object id when there is one. * @param mixed $user The user being checked. * @return array */ public function filter( $allcaps, $caps, $args, $user ) { if ( ! $user instanceof \WP_User || 0 !== (int) $user->ID ) { return $allcaps; } $allcaps = is_array( $allcaps ) ? $allcaps : array(); $object = is_array( $args ) && isset( $args[2] ) && is_numeric( $args[2] ) ? (int) $args[2] : 0; if ( null === $this->ids || ( 0 !== $object && in_array( $object, $this->ids, true ) ) ) { foreach ( (array) $caps as $cap ) { if ( in_array( $cap, self::GRANTED_FOR_PRODUCT, true ) ) { $allcaps[ $cap ] = true; } } } foreach ( self::DENIED as $cap ) { $allcaps[ $cap ] = false; } return $allcaps; } /** * @return array The product and its variations. */ private static function with_variations( int $product_id ): array { $product = wc_get_product( $product_id ); $ids = array( $product_id ); if ( $product instanceof \WC_Product ) { foreach ( $product->get_children() as $child ) { $ids[] = (int) $child; } } return $ids; } }