Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/autoregister facets #78

Draft
wants to merge 5 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 23 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,29 @@

## [Unreleased]

- feat: Add Facets as `FacetsInput` where args on `RootQuery` connections to post types.
- feat!: Change `Facet.type` from type `String` to type `FacetTypeEnum`.

## v0.5.1

This _minor_ release bumps the `tested up to` tags for WordPress v6.7.2 and WPGraphQL v2.0.

It's also the first release in the new repository location at https://github.com/AxeWP/wp-graphql-facetwp. should allow @justlevine to allocate significantly more resources to the plugin. Thanks @hsimah for your stewardship and hospitality 🙏.

> [!IMPORTANT]
> Plugin dependencies are no longer version controlled, and are instead built during release.
>
> For those not using a package manager, you should now download the `wp-graphql-facetwp.php` from the [latest GitHub Release page](https://github.com/AxeWP/wp-graphql-facetwp/releases/latest).
>
> If you are using Composer, you can continue to install the plugin using `composer require hsimah-services/wp-graphql-facetwp`.


- chore!: Remove `vendor` and `vendor-prefixed/*` from the GitHub repository.
- chore: Update Strauss to v0.19.4 and Composer dependencies.
- chore: Update repository URLs to `https://github.com/AxeWP/wp-graphql-facetwp`.
- chore: Test compatibility with WordPress 6.7.2.
- chore: Test compatibility with WPGraphQL 2.0.
- chore: Update Strauss to v0.19.1.
- chore: Update composer dependencies.
- chore: Update repository URLs to `https://github.com/AxeWP/wp-graphql-facetwp`. H/t @hsimah
- ci: Cleanup zip and release workflow.
- ci: Use `docker compose` instead of `docker-compose`.

Expand Down Expand Up @@ -46,7 +66,7 @@ This _minor_ release adds support for the [Sort Facet](https://facetwp.com/help-

## v0.4.2

This _minor_ release lays the groundwork for the upcoming Facet autoregistration / official Sort Facet support. It introduces a new `FacetConfig` interface, which is implemented by the `Facet` object. Additionally, we adopted the use of WPGraphQL Plugin Boilerplate to scaffold our PHP classes, updated our Composer dev dependencies, and started testing against WordPress 6.2 and running WPUnit tests as part of our CI workflow.
This _minor_ release lays the groundwork for the upcoming Facet auto-registration / official Sort Facet support. It introduces a new `FacetConfig` interface, which is implemented by the `Facet` object. Additionally, we adopted the use of WPGraphQL Plugin Boilerplate to scaffold our PHP classes, updated our Composer dev dependencies, and started testing against WordPress 6.2 and running WPUnit tests as part of our CI workflow.

- feat: Change `Facet` object to implement new `FacetConfig` interface.
- fix: Add missing descriptions to GraphQL types.
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Adds WPGraphQL support for [FacetWP](https://facetwp.com/).
* [Documentation](#usage)
-----

![Packagist License](https://img.shields.io/packagist/l/hsimah-services/wp-graphql-facetwp?color=green) ![Packagist Version](https://img.shields.io/packagist/v/hsimah-services/wp-graphql-facetwp?label=stable) ![GitHub commits since latest release (by SemVer)](https://img.shields.io/github/commits-since/AxeWP/wp-graphql-facetwp/0.5.0) ![GitHub forks](https://img.shields.io/github/forks/AxeWP/wp-graphql-facetwp?style=social) ![GitHub Repo stars](https://img.shields.io/github/stars/AxeWP/wp-graphql-facetwp?style=social)<br />
![Packagist License](https://img.shields.io/packagist/l/hsimah-services/wp-graphql-facetwp?color=green) ![Packagist Version](https://img.shields.io/packagist/v/hsimah-services/wp-graphql-facetwp?label=stable) ![GitHub commits since latest release (by SemVer)](https://img.shields.io/github/commits-since/AxeWP/wp-graphql-facetwp/0.5.1) ![GitHub forks](https://img.shields.io/github/forks/AxeWP/wp-graphql-facetwp?style=social) ![GitHub Repo stars](https://img.shields.io/github/stars/AxeWP/wp-graphql-facetwp?style=social)<br />
![CodeQuality](https://img.shields.io/github/actions/workflow/status/AxeWP/wp-graphql-facetwp/code-quality.yml?branch=develop&label=Code%20Quality)
![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/AxeWP/wp-graphql-facetwp/integration-testing.yml?branch=develop&label=Integration%20Testing)
![Coding Standards](https://img.shields.io/github/actions/workflow/status/AxeWP/wp-graphql-facetwp/code-standard.yml?branch=develop&label=WordPress%20Coding%20Standards)
Expand Down Expand Up @@ -55,7 +55,7 @@ Until we hit v1.0, we're using a modified version of [SemVer](https://semver.org

## Development and Support

WPGraphQL for FacetWP was initially created by [Hamish Blake](https://www.hsimah.com/). Maintainance and development are now provided by [AxePress Development](https://axepress.dev/). On 15 February 2025, the repository was transferred from https://github.com/hsimah-services to https://github.com/AxeWP/wp-graphql-facetwp.
WPGraphQL for FacetWP was initially created by [Hamish Blake](https://www.hsimah.com/). Maintenance and development are now provided by [AxePress Development](https://axepress.dev/). On 15 February 2025, the repository was transferred from https://github.com/hsimah-services to https://github.com/AxeWP/wp-graphql-facetwp.

Community contributions are _welcome_ and **encouraged**.

Expand Down
2 changes: 1 addition & 1 deletion access-functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ static function () use ( $type_name ) {
/**
* Get the facets that are allowed to be queried via GraphQL.
*
* @return array<string,mixed>
* @return array<string,array<string, mixed>>
*
* @since 0.4.1
*/
Expand Down
2 changes: 1 addition & 1 deletion readme.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Tags: GraphQL, Headless, WPGraphQL, Facet, FacetWP, WPGraphQL, FacetWP, React,
Requires at least: 5.4.1
Requires PHP: 7.4
Tested up to: 6.7.2
Stable tag: 0.5.0
Stable tag: 0.5.1
Maintained at: https://github.com/AxeWP/wp-graphql-facetwp
License: GPL-3
License URI: https://www.gnu.org/licenses/gpl-3.0.html
Expand Down
142 changes: 142 additions & 0 deletions src/CoreSchemaFilters.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

namespace WPGraphQL\FacetWP;

use WPGraphQL\Data\Connection\AbstractConnectionResolver;
use WPGraphQL\Data\Connection\PostObjectConnectionResolver;
use WPGraphQL\FacetWP\Vendor\AxeWP\GraphQL\Interfaces\Registrable;

/**
Expand All @@ -20,6 +22,10 @@ class CoreSchemaFilters implements Registrable {
public static function init(): void {
// Prefix the GraphQL type names.
add_filter( 'graphql_facetwp_type_prefix', [ self::class, 'get_type_prefix' ] );

// Filter the ConnectionResolver to use FacetWP.
add_filter( 'graphql_connection_query_args', [ self::class, 'filter_connection_query_args' ], 10, 3 );
add_filter( 'graphql_connection_query', [ self::class, 'filter_connection_query' ], 10, 2 );
}

/**
Expand All @@ -28,4 +34,140 @@ public static function init(): void {
public static function get_type_prefix(): string {
return '';
}

/**
* Filters connection query args.
*
* @param array<string,mixed> $query_args Query args.
* @param \WPGraphQL\Data\Connection\AbstractConnectionResolver<mixed> $connection_resolver Connection resolver.
* @param array<string,mixed> $args Connection args.
*
* @return array<string,mixed>
*/
public static function filter_connection_query_args( array $query_args, AbstractConnectionResolver $connection_resolver, array $args ): array {
if ( empty( $args['where']['facets'] ) ) {
return $query_args;
}

$facet_query = self::parse_facet_query_args( $args['where']['facets'] );

$fwp_args = [
'facets' => $facet_query,
'query_args' => $query_args,
];

$payload = FWP()->api->process_request( $fwp_args );

return [
'post__in' => ! empty( $payload['results'] ) ? $payload['results'] : [ 'no_results' ], // We use 'no_results' as a special value to indicate that the facet has no results.
'fields' => 'ids',
'__fwp_payload' => $payload,
];
}

/**
* Filters the Connection Query to handle a facet with no results.
*
* @param mixed $query The query to filter.
* @param \WPGraphQL\Data\Connection\AbstractConnectionResolver<mixed> $resolver The resolver.
*
* @return mixed|null
*/
public static function filter_connection_query( $query, AbstractConnectionResolver $resolver ) {
if ( ! $resolver instanceof PostObjectConnectionResolver ) {
return $query;
}

$query_args = $resolver->get_query_args();

/**
* Store the payload on the query object so that it can be accessed in the connection.
*/

// If FWP finds no results, return null.
if ( isset( $query_args['post__in'] ) && 'no_results' === $query_args['post__in'][0] ) {
$resolver->set_query_arg( 'post__in', [] );
return null;
}

return $query;
}

/**
* Parses the facet query args from the where args provided in the GraphQL query.
*
* @param array<string,array<string,mixed>> $args The facet query args.
*
* @return array<string,mixed>
*/
protected static function parse_facet_query_args( array $args ): array {
$facets = get_graphql_allowed_facets();
$field_names = array_column( $facets, 'graphql_field_name' );

// Bail early if there are no facets.
if ( empty( $facets ) ) {
return [];
}

$facet_args = [];
foreach ( $args as $field_name => $value ) {
/**
* If the facet has a graphql_single_name, use that instead of the field name.
*
* @var string[] $field_names
* @var string|false $key
*/
$key = array_search( $field_name, $field_names, true );

if ( false === $key ) {
continue;
}

switch ( $facets[ $key ]['type'] ) {
case 'checkboxes':
case 'fselect':
case 'rating':
case 'radio':
case 'dropdown':
case 'hierarchy':
case 'search':
case 'autocomplete':
$facet_args[ $facets[ $key ]['name'] ] = $value;
break;
case 'slider':
case 'date_range':
case 'number_range':
if ( isset( $value['min'] ) ) {
$facet_args[ $facets[ $key ]['name'] ]['min'] = $value['min'];
}
if ( isset( $value['max'] ) ) {
$facet_args[ $facets[ $key ]['name'] ]['max'] = $value['max'];
}
break;
case 'proximity':
if ( isset( $value['latitude'] ) ) {
$facet_args[ $facets[ $key ]['name'] ]['latitude'] = $value['latitude'];
}
if ( isset( $value['longitude'] ) ) {
$facet_args[ $facets[ $key ]['name'] ]['longitude'] = $value['longitude'];
}
if ( isset( $value['chosenRadius'] ) ) {
$facet_args[ $facets[ $key ]['name'] ]['chosenRadius'] = $value['chosenRadius'];
}
if ( isset( $value['locationName'] ) ) {
$facet_args[ $facets[ $key ]['name']['locationName'] ] = $value['locationName'];
}
break;
default:
$arg_value = apply_filters( 'facetwp_facet_query_arg_value', null, $value, $facets[ $key ]['type'], $facets[ $key ]['name'], $field_name, $args ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound

if ( null !== $arg_value ) {
$facet_args[ $facets[ $key ]['name'] ] = $arg_value;
}
}
}

/** @var array<string,mixed> $facet_args */
return $facet_args;
}
}
2 changes: 1 addition & 1 deletion src/Registry/FacetRegistry.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class FacetRegistry {
/**
* Gets the facet configs to be registered to WPGraphQL.
*
* @return array<string,mixed>[]
* @return array<string, array<string,mixed>>
*
* @since 0.4.1
*/
Expand Down
4 changes: 4 additions & 0 deletions src/Registry/TypeRegistry.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ private static function enums(): array {
// Enums to register.
$classes_to_register = [
Enum\ProximityRadiusOptions::class,
Enum\FacetTypeEnum::class,
Enum\SortOptionsEnum::class,
];

Expand All @@ -69,6 +70,7 @@ private static function enums(): array {
*/
private static function inputs(): array {
$classes_to_register = [
Input\FacetsInput::class,
Input\DateRangeArgs::class,
Input\NumberRangeArgs::class,
Input\ProximityArgs::class,
Expand All @@ -93,6 +95,7 @@ private static function inputs(): array {
private static function interfaces(): array {
$classes_to_register = [
WPInterface\FacetConfig::class,
WPInterface\NodeWithFacets::class,
];

/**
Expand All @@ -119,6 +122,7 @@ public static function objects(): array {
WPObject\FacetSortOptionSetting::class,
WPObject\FacetSettings::class,
WPObject\Facet::class,
WPObject\FacetObjects::class,
];

/**
Expand Down
53 changes: 53 additions & 0 deletions src/Type/Enum/FacetTypeEnum.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php
/**
* GraphQL Enum Type - FacetTypeEnum.
*
* @package WPGraphQL\FacetWP\Type\Enum
* @since @todo
*/

namespace WPGraphQL\FacetWP\Type\Enum;

use WPGraphQL\FacetWP\Vendor\AxeWP\GraphQL\Abstracts\EnumType;
use WPGraphQL\Type\WPEnumType;

/**
* Class - FacetTypeEnum
*/
class FacetTypeEnum extends EnumType {
/**
* {@inheritDoc}
*/
protected static function type_name(): string {
return 'FacetTypeEnum';
}

/**
* {@inheritDoc}
*/
public static function get_description(): string {
return __( 'The facet type', 'wpgraphql-facetwp' );
}

/**
* {@inheritDoc}
*/
public static function get_values(): array {
$facet_types = FWP()->helper->get_facet_types();

$values = [];

foreach ( $facet_types as $key => $obj ) {
$values[ WPEnumType::get_safe_name( $key ) ] = [
'description' => sprintf(
// translators: %s is the facet type.
__( 'The %s facet type', 'wpgraphql-facetwp' ),
$obj->label,
),
'value' => $key,
];
}

return $values;
}
}
Loading