*/ public static function types(): array { $scopes = array( self::READ, self::WRITE ); /** * Sign-in types offered, each with the scopes it can grant. Remove a * key to stop offering that type. * * @param array $types e.g. [ 'direct' => [...], 'device' => [...] ]. */ return (array) apply_filters( 'agentadapt_signin_types', array( 'direct' => $scopes, 'device' => $scopes ) ); } /** * Whether a type is offered. * * @param string $type A sign-in type key, such as direct. */ public static function offers( string $type ): bool { return isset( self::types()[ $type ] ); } /** * Plain-language description of every scope this store grants, the * customer-facing wording on consent pages and in Connected agents. * * @return array */ public static function descriptions(): array { /** * Scope descriptions. Premium custom scopes add theirs. * * @param array $descriptions Scope → description. */ return (array) apply_filters( 'agentadapt_scope_descriptions', array( self::READ => __( 'View your orders, their status and your saved addresses', 'agent-adapt' ), self::WRITE => __( 'Make changes for you, such as cancelling an order you have not paid for', 'agent-adapt' ), ) ); } /** * Scopes this store defines beyond poppy:read and poppy:write, each with * its description (spec 4.4, `auth.custom_scopes`). Only scopes some * offered sign-in type can actually grant are listed. * * @return array */ public static function custom_scopes(): array { $grantable = array_unique( array_merge( array(), ...array_values( self::types() ) ) ); $out = array(); foreach ( self::descriptions() as $scope => $text ) { if ( ! in_array( $scope, array( self::READ, self::WRITE ), true ) && in_array( $scope, $grantable, true ) ) { $out[ $scope ] = $text; } } return $out; } /** * The requested scopes this agent may be granted. A merchant can cap an * agent below what the sign-in type allows; the customer is then offered * only the scopes inside the cap (spec 4.4: the User may approve fewer * scopes than requested, and the agent checks `scope`). * * @param string[] $scopes Parsed, requested scopes. * @param string $client_hash Agent hash. * @return string[] */ public static function for_agent( array $scopes, string $client_hash ): array { /** * The most this agent may be granted. Only an intersection is applied, * so a filter can narrow the request but never add to it. * * @param string[] $scopes Requested scopes. * @param string $client_hash Agent hash. */ $cap = (array) apply_filters( 'agentadapt_agent_scope_ceiling', $scopes, $client_hash ); return array_values( array_intersect( $scopes, $cap ) ); } /** * Parse a requested scope string for a sign-in type. * * @param string $scope Space-separated scopes. * @param string $type Sign-in type. * @return string[]|WP_Error The scopes, or invalid_scope (spec 4.4: missing, unknown or unavailable). */ public static function parse( string $scope, string $type ) { $asked = array_values( array_unique( array_filter( explode( ' ', trim( $scope ) ) ) ) ); if ( ! $asked ) { return new WP_Error( 'invalid_scope', 'scope is required' ); } $allowed = self::types()[ $type ] ?? array(); foreach ( $asked as $s ) { if ( ! in_array( $s, $allowed, true ) ) { return new WP_Error( 'invalid_scope', 'scope ' . $s . ' is not available here' ); } } return $asked; } }