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

Facet by Meta #2954

Merged
merged 15 commits into from
Aug 26, 2022
Merged
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
1 change: 1 addition & 0 deletions .wp-env.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"https://downloads.wordpress.org/plugin/woocommerce.zip"
],
"mappings": {
"wp-content/mu-plugins/enable-meta-facet.php": "./tests/cypress/wordpress-files/test-mu-plugins/enable-meta-facet.php",
"wp-content/mu-plugins/unique-index-name.php": "./tests/cypress/wordpress-files/test-mu-plugins/unique-index-name.php",
"wp-content/plugins/cpt-and-custom-tax.php": "./tests/cypress/wordpress-files/test-plugins/cpt-and-custom-tax.php",
"wp-content/plugins/fake-new-activation.php": "./tests/cypress/wordpress-files/test-plugins/fake-new-activation.php",
Expand Down
34 changes: 34 additions & 0 deletions assets/js/blocks/facets/meta/block.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 2,
"title": "Facet by Meta (ElasticPress)",
"textdomain": "elasticpress",
"name": "elasticpress/facet-meta",
"icon": "feedback",
"category": "widgets",
"attributes": {
"searchPlaceholder": {
"type": "string",
"default": "Search"
},
"facet": {
"type": "string",
"default": ""
},
"orderby": {
"type" : "string",
"default": "count",
"enum" : [ "count", "name" ]
},
"order": {
"type": "string",
"default": "desc",
"enum": [ "desc", "asc" ]
}
},
"supports": {
"html": false
},
"editorScript": "file:/../../../../../dist/js/facets-meta-block-script.min.js",
"style": "file:/../../../../../dist/css/facets-styles.min.css"
}
105 changes: 105 additions & 0 deletions assets/js/blocks/facets/meta/edit.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { InspectorControls, useBlockProps } from '@wordpress/block-editor';
import {
PanelBody,
RadioControl,
TextControl,
Spinner,
Placeholder,
SelectControl,
} from '@wordpress/components';
import { Fragment, useEffect, useState, useCallback } from '@wordpress/element';
import apiFetch from '@wordpress/api-fetch';
import { __ } from '@wordpress/i18n';

const FacetBlockEdit = (props) => {
const { attributes, setAttributes } = props;
const [metaKeys, setMetaKeys] = useState([]);
const [preview, setPreview] = useState('');
const [loading, setLoading] = useState(false);
const { searchPlaceholder, facet, orderby, order } = attributes;

const blockProps = useBlockProps();

const load = useCallback(async () => {
const metaKeys = await apiFetch({
path: '/elasticpress/v1/facets/meta/keys',
});
setMetaKeys(metaKeys);
}, [setMetaKeys]);

useEffect(load, [load]);

useEffect(() => {
setLoading(true);
const params = new URLSearchParams({
searchPlaceholder,
facet,
orderby,
order,
});
apiFetch({
path: `/elasticpress/v1/facets/meta/block-preview?${params}`,
})
.then((preview) => setPreview(preview))
.finally(() => setLoading(false));
}, [searchPlaceholder, facet, orderby, order]);

return (
<Fragment>
<InspectorControls>
<PanelBody title={__('Facet Settings', 'elasticpress')}>
<TextControl
label={__('Search Placeholder', 'elasticpress')}
value={searchPlaceholder}
onChange={(value) => setAttributes({ searchPlaceholder: value })}
/>
<SelectControl
label={__('Meta Field Key', 'elasticpress')}
help={__(
'This is the list of meta fields indexed in Elasticsearch. If you do not see your field here, you might need to sync your content.',
'elasticpress',
)}
value={facet}
options={[
...metaKeys.map((metaKey) => ({
label: metaKey,
value: metaKey,
})),
]}
onChange={(value) => setAttributes({ facet: value })}
/>
<RadioControl
label={__('Order By', 'elasticpress')}
help={__('The field used to order available options', 'elasticpress')}
selected={orderby}
options={[
{ label: __('Count', 'elasticpress'), value: 'count' },
{ label: __('Name', 'elasticpress'), value: 'name' },
]}
onChange={(value) => setAttributes({ orderby: value })}
/>
<RadioControl
label={__('Order', 'elasticpress')}
selected={order}
options={[
{ label: __('ASC', 'elasticpress'), value: 'asc' },
{ label: __('DESC', 'elasticpress'), value: 'desc' },
]}
onChange={(value) => setAttributes({ order: value })}
/>
</PanelBody>
</InspectorControls>

<div {...blockProps}>
{loading && (
<Placeholder>
<Spinner />
</Placeholder>
)}
{/* eslint-disable-next-line react/no-danger */}
{!loading && <div dangerouslySetInnerHTML={{ __html: preview }} />}
</div>
</Fragment>
);
};
export default FacetBlockEdit;
15 changes: 15 additions & 0 deletions assets/js/blocks/facets/meta/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/**
* WordPress dependencies.
*/
import { registerBlockType } from '@wordpress/blocks';

/**
* Internal dependencies.
*/
import edit from './edit';
import block from './block.json';

registerBlockType(block, {
edit,
save: () => {},
});
1 change: 1 addition & 0 deletions includes/classes/Elasticsearch.php
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,7 @@ public function query( $index, $type, $query, $query_args, $query_object = null
[
'found_documents' => $total_hits,
'documents' => $documents,
'aggregations' => $response['aggregations'] ?? [],
],
$response,
$query,
Expand Down
217 changes: 217 additions & 0 deletions includes/classes/Feature/Facets/Types/Meta/Block.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
<?php
/**
* Facets block
*
* @since 4.2.0
* @package elasticpress
*/

namespace ElasticPress\Feature\Facets\Types\Meta;

use ElasticPress\Features;

if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}

/**
* Facets block class
*/
class Block {
/**
* Hook block funcionality.
*/
public function setup() {
add_action( 'init', [ $this, 'register_block' ] );
add_action( 'rest_api_init', [ $this, 'setup_endpoints' ] );
}

/**
* Setup REST endpoints for the feature.
*/
public function setup_endpoints() {
register_rest_route(
'elasticpress/v1',
'facets/meta/keys',
[
'methods' => 'GET',
'permission_callback' => [ $this, 'check_facets_meta_rest_permission' ],
'callback' => [ $this, 'get_rest_registered_metakeys' ],
]
);
register_rest_route(
'elasticpress/v1',
'facets/meta/block-preview',
[
'methods' => 'GET',
'permission_callback' => [ $this, 'check_facets_meta_rest_permission' ],
'callback' => [ $this, 'render_block_preview' ],
'args' => [
'searchPlaceholder' => [
'sanitize_callback' => 'sanitize_text_field',
],
'facet' => [
'sanitize_callback' => 'sanitize_text_field',
],
'orderby' => [
'sanitize_callback' => 'sanitize_text_field',
],
'order' => [
'sanitize_callback' => 'sanitize_text_field',
],
],

]
);
}

/**
* Check permissions of the /facets/taxonomies REST endpoint.
*
* @return WP_Error|true
*/
public function check_facets_meta_rest_permission() {
if ( ! current_user_can( 'edit_theme_options' ) ) {
return new \WP_Error( 'ep_rest_forbidden', esc_html__( 'Sorry, you cannot view this resource.', 'elasticpress' ), array( 'status' => 401 ) );
}

return true;
}

/**
* Return an array of registered meta keys.
*
* @return array
*/
public function get_rest_registered_metakeys() {
$post_indexable = \ElasticPress\Indexables::factory()->get( 'post' );

try {
$meta_keys = $post_indexable->get_distinct_meta_field_keys();
} catch ( \Throwable $th ) {
$meta_keys = [];
}

return $meta_keys;
}

/**
* Register the block.
*/
public function register_block() {
register_block_type_from_metadata(
EP_PATH . 'assets/js/blocks/facets/meta',
[
'render_callback' => [ $this, 'render_block' ],
]
);
}

/**
* Render the block.
*
* @param array $attributes Block attributes.
*/
public function render_block( $attributes ) {
$attributes = $this->parse_attributes( $attributes );

/** This filter is documented in includes/classes/Feature/Facets/Types/Taxonomy/Block.php */
$renderer_class = apply_filters( 'ep_facet_renderer_class', __NAMESPACE__ . '\Renderer', 'meta', 'block', $attributes );
$renderer = new $renderer_class();

ob_start();
?>
<div class="wp-block-elasticpress-facet">
<?php $renderer->render( [], $attributes ); ?>
</div>
<?php
return ob_get_clean();
}

/**
* Outputs the block preview
*
* @param \WP_REST_Request $request REST request
* @return string
*/
public function render_block_preview( $request ) {
global $wp_query;

add_filter( 'ep_is_facetable', '__return_true' );
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would it make sense to provide more context in the filter to be able to make a decision?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in f9ae942. I will apply a similar change to #2919 as well, especially for the renderer class part.


$search = Features::factory()->get_registered_feature( 'search' );

$attributes = $this->parse_attributes(
[
'searchPlaceholder' => $request->get_param( 'searchPlaceholder' ),
'facet' => $request->get_param( 'facet' ),
'orderby' => $request->get_param( 'orderby' ),
'order' => $request->get_param( 'order' ),
]
);

add_filter(
'ep_facet_meta_fields',
function ( $meta_fields ) use ( $attributes ) {
$meta_fields = [ $attributes['facet'] ];
return $meta_fields;
}
);

$wp_query = new \WP_Query(
[
'post_type' => $search->get_searchable_post_types(),
'per_page' => 1,
]
);

/** This filter is documented in includes/classes/Feature/Facets/Types/Taxonomy/Block.php */
$renderer_class = apply_filters( 'ep_facet_renderer_class', __NAMESPACE__ . '\Renderer', 'meta', 'block', $attributes );
$renderer = new $renderer_class();

ob_start();
$renderer->render( [], $attributes );
$block_content = ob_get_clean();

if ( empty( $block_content ) ) {
if ( empty( $attributes['facet'] ) ) {
return esc_html__( 'Preview not available', 'elasticpress' );
}

return sprintf(
/* translators: Meta field name */
esc_html__( 'Preview for %s not available', 'elasticpress' ),
esc_html( $request->get_param( 'facet' ) )
);
}

$block_content = preg_replace( '/href="(.*?)"/', 'href="#"', $block_content );
return '<div class="wp-block-elasticpress-facet">' . $block_content . '</div>';
}

/**
* Utilitary method to set default attributes.
*
* @param array $attributes Attributes passed
* @return array
*/
protected function parse_attributes( $attributes ) {
$attributes = wp_parse_args(
$attributes,
[
'searchPlaceholder' => esc_html_x( 'Search', 'Facet by meta search placeholder', 'elasticpress' ),
'facet' => '',
'orderby' => 'count',
'order' => 'desc',

]
);
if ( empty( $attributes['facet'] ) ) {
$registered_metakeys = $this->get_rest_registered_metakeys();
if ( ! empty( $registered_metakeys ) ) {
$attributes['facet'] = reset( $registered_metakeys );
}
}
return $attributes;
}
}
Loading