Preview: Utils.php
Size: 7.68 KB
/home/nshryvcy/radiantskinclinics.org/wp-content/plugins/woocommerce/src/Internal/Api/Utils.php
<?php
declare(strict_types=1);
namespace Automattic\WooCommerce\Internal\Api;
/**
* Shared utilities for the auto-generated GraphQL resolvers.
*/
class Utils {
/**
* Assert that the current user has the given WordPress capability.
*
* Throws a GraphQL UNAUTHORIZED error if the check fails. Intended to
* be called from generated resolver methods so the capability-check
* boilerplate doesn't have to be repeated in every resolver.
*
* @param string $capability A WordPress capability slug.
*
* @throws \Automattic\WooCommerce\Vendor\GraphQL\Error\Error When the current user lacks the capability.
*/
public static function check_current_user_can( string $capability ): void {
if ( ! current_user_can( $capability ) ) {
throw new \Automattic\WooCommerce\Vendor\GraphQL\Error\Error(
'You do not have permission to perform this action.',
extensions: array( 'code' => 'UNAUTHORIZED' )
);
}
}
/**
* Compute the complexity cost of a paginated connection field.
*
* Used as the `complexity` callable on every generated resolver field
* that returns a `Connection`. Runs during query validation (before
* resolver execution, so before `PaginationParams::validate_args()` has
* a chance to reject bad input) — so out-of-range / wrong-type values
* are clamped to MAX_PAGE_SIZE here. Using MAX_PAGE_SIZE as the
* fallback means a malicious attempt to shrink cost via e.g. a
* negative `first` value only inflates the computed complexity,
* closing the cost-bypass angle.
*
* @param int $child_complexity The complexity of a single child node.
* @param array $args The field arguments (expects `first` / `last`).
*
* @return int The total complexity for this connection field.
*/
public static function complexity_from_pagination( int $child_complexity, array $args ): int {
$requested = $args['first'] ?? $args['last'] ?? \Automattic\WooCommerce\Api\Pagination\PaginationParams::get_default_page_size();
$page_size = ( is_int( $requested ) && $requested >= 0 && $requested <= \Automattic\WooCommerce\Api\Pagination\PaginationParams::MAX_PAGE_SIZE )
? $requested
: \Automattic\WooCommerce\Api\Pagination\PaginationParams::MAX_PAGE_SIZE;
return $page_size * ( $child_complexity + 1 );
}
/**
* Build a PaginationParams instance from the standard GraphQL pagination
* arguments (first, last, after, before).
*
* @param array $args The GraphQL field arguments.
*
* @return \Automattic\WooCommerce\Api\Pagination\PaginationParams
* @throws \Automattic\WooCommerce\Vendor\GraphQL\Error\Error When a pagination value is out of range.
*/
public static function create_pagination_params( array $args ): \Automattic\WooCommerce\Api\Pagination\PaginationParams {
return self::create_input(
fn() => new \Automattic\WooCommerce\Api\Pagination\PaginationParams(
first: $args['first'] ?? null,
last: $args['last'] ?? null,
after: $args['after'] ?? null,
before: $args['before'] ?? null,
)
);
}
/**
* Invoke a factory callable, catching InvalidArgumentException and
* converting it to a client-visible GraphQL error.
*
* Used to wrap construction of unrolled input types (PaginationParams,
* ProductFilterInput, etc.) whose constructors may validate their
* arguments and throw.
*
* @param callable $factory A callable that returns the constructed object.
*
* @return mixed The return value of the factory.
* @throws \Automattic\WooCommerce\Vendor\GraphQL\Error\Error When the factory throws InvalidArgumentException.
*/
public static function create_input( callable $factory ): mixed {
// phpcs:disable WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Not HTML; serialized as JSON.
try {
return $factory();
} catch ( \InvalidArgumentException $e ) {
throw new \Automattic\WooCommerce\Vendor\GraphQL\Error\Error(
$e->getMessage(),
extensions: array( 'code' => 'INVALID_ARGUMENT' )
);
}
// phpcs:enable WordPress.Security.EscapeOutput.ExceptionNotEscaped
}
/**
* Execute a command's execute() method, translating any thrown exceptions
* into spec-compliant GraphQL errors.
*
* @param object $command The command instance (must have an execute() method).
* @param array $execute_args Named arguments to pass to execute().
*
* @return mixed The return value of execute().
* @throws \Automattic\WooCommerce\Vendor\GraphQL\Error\Error On any exception from the command.
*/
public static function execute_command( object $command, array $execute_args ): mixed {
return self::translate_exceptions(
static fn() => $command->execute( ...$execute_args )
);
}
/**
* Invoke a command's authorize() method, translating any thrown exceptions
* into spec-compliant GraphQL errors.
*
* Mirror of execute_command() for the authorize step. Needed because an
* authorize() call can throw an ApiException (e.g. AuthorizationException
* when a target record does not exist); without this wrapper the
* exception would propagate up to webonyx and lose its error code and
* user-visible message on its way through the generic error formatter.
*
* @param object $command The command instance (must have an authorize() method).
* @param array $authorize_args Named arguments to pass to authorize().
*
* @return bool The return value of authorize().
* @throws \Automattic\WooCommerce\Vendor\GraphQL\Error\Error On any exception from the authorize method.
*/
public static function authorize_command( object $command, array $authorize_args ): bool {
return self::translate_exceptions(
static fn() => $command->authorize( ...$authorize_args )
);
}
/**
* Invoke a callable, translating any thrown exception into a
* spec-compliant GraphQL error with a machine-readable code.
*
* - ApiException → its own code + extensions, with the original message.
* - InvalidArgumentException → INVALID_ARGUMENT, with the original message.
* - Any other Throwable → INTERNAL_ERROR, with a generic message; the
* original throwable is attached as `previous` for debug-mode surfacing.
*
* Public so that generated resolvers can wrap Code-API calls that happen
* outside the execute()/authorize() pair (e.g. the Connection::slice()
* call emitted for nested paginated connection fields, which can throw
* InvalidArgumentException when pagination bounds are exceeded).
*
* @param callable $operation Callable to invoke.
*
* @return mixed The return value of the callable.
* @throws \Automattic\WooCommerce\Vendor\GraphQL\Error\Error On any exception from the callable.
*/
public static function translate_exceptions( callable $operation ): mixed {
// phpcs:disable WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Not HTML; serialized as JSON.
try {
return $operation();
} catch ( \Automattic\WooCommerce\Api\ApiException $e ) {
// Caller-supplied extensions come first so the canonical
// getErrorCode() can't be silently overridden by an extensions
// entry keyed 'code'. The invariant "the code on the wire
// equals ApiException::getErrorCode()" is worth enforcing.
throw new \Automattic\WooCommerce\Vendor\GraphQL\Error\Error(
$e->getMessage(),
extensions: array_merge(
$e->getExtensions(),
array( 'code' => $e->getErrorCode() )
)
);
} catch ( \InvalidArgumentException $e ) {
throw new \Automattic\WooCommerce\Vendor\GraphQL\Error\Error(
$e->getMessage(),
extensions: array( 'code' => 'INVALID_ARGUMENT' )
);
} catch ( \Throwable $e ) {
throw new \Automattic\WooCommerce\Vendor\GraphQL\Error\Error(
'An unexpected error occurred.',
previous: $e,
extensions: array( 'code' => 'INTERNAL_ERROR' )
);
}//end try
// phpcs:enable WordPress.Security.EscapeOutput.ExceptionNotEscaped
}
}
Directory Contents
Dirs: 2 × Files: 7