Skip to content
Open
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
58 changes: 42 additions & 16 deletions assets/js/ordering/pointers.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export class Pointers extends Component {
searchText: '',
searchResults: {},
removedPointers: [],
excludedPosts: window.epOrdering.excluded_posts || [],
};
}

Expand Down Expand Up @@ -121,27 +122,34 @@ export class Pointers extends Component {

removePointer = (pointer) => {
let { pointers } = this.state;
const { removedPointers } = this.state;
const { removedPointers, excludedPosts } = this.state;

delete pointers[pointers.indexOf(pointer)];
pointers = pointers.filter((item) => item !== null);
removedPointers.push(pointer.ID);

this.setState({ pointers });
if (pointer.type === 'custom-result') {
excludedPosts.push(pointer.ID);
} else {
removedPointers.push(pointer.ID);
}

this.setState({ pointers, removedPointers, excludedPosts });
};

getMergedPosts = () => {
let { pointers } = this.state;
const { title, defaultResults } = this.state;
const { title, defaultResults, excludedPosts } = this.state;
let merged = defaultResults[title].slice();

pointers = pointers.sort((a, b) => {
return a.order > b.order ? 1 : -1;
});
const pointersIds = pluck(pointers, 'ID');

// Remove all custom pointers from the default results
merged = merged.filter((item) => pointersIds.indexOf(item.ID) === -1);
// Remove all custom pointers and excluded posts from the default results
merged = merged.filter(
(item) => pointersIds.indexOf(item.ID) === -1 && excludedPosts.indexOf(item.ID) === -1,
);

// Insert pointers into their proper location
pointers.forEach((pointer) => {
Expand Down Expand Up @@ -185,6 +193,7 @@ export class Pointers extends Component {
addPointer = (post) => {
const id = post.ID;
const { posts, pointers } = this.state;
let { excludedPosts, removedPointers } = this.state;

if (!posts[id]) {
posts[id] = post;
Expand All @@ -202,13 +211,17 @@ export class Pointers extends Component {
return;
}

// Remove from excluded/removed lists if being re-added
excludedPosts = excludedPosts.filter((item) => item !== id);
removedPointers = removedPointers.filter((item) => item !== id);

pointers.push({
ID: id,
order: position,
type: 'custom-result',
});

this.setState({ pointers });
this.setState({ pointers, excludedPosts, removedPointers });
};

/**
Expand Down Expand Up @@ -312,6 +325,7 @@ export class Pointers extends Component {
title,
pointers,
removedPointers,
excludedPosts,
searchText,
searchResults: searchResultsFromState,
} = this.state;
Expand Down Expand Up @@ -357,6 +371,7 @@ export class Pointers extends Component {
<div>
<input type="hidden" name="search-ordering-nonce" value={window.epOrdering.nonce} />
<input type="hidden" name="ordered_posts" value={JSON.stringify(pointers)} />
<input type="hidden" name="excluded_posts" value={JSON.stringify(excludedPosts)} />
<DragDropContext onDragEnd={this.onDragComplete}>
<Droppable droppableId="droppable">
{(provided) => (
Expand All @@ -383,13 +398,16 @@ export class Pointers extends Component {

// Determine if this result is part of default search results or not
const itemType = item?.type || 'reordered';
const tooltipText =
itemType === 'reordered'
? __('Return to original position', 'elasticpress')
: __(
'Remove custom result from results list',
'elasticpress',
);
const isCustomResult = itemType === 'custom-result';
const tooltipText = isCustomResult
? __(
'Remove custom result from results list',
'elasticpress',
)
: __('Return to original position', 'elasticpress');
const iconClass = isCustomResult
? 'dashicons dashicons-trash delete-pointer'
: 'dashicons dashicons-undo delete-pointer';

return (
<Fragment key={item.ID}>
Expand Down Expand Up @@ -445,7 +463,7 @@ export class Pointers extends Component {
role="button"
tabIndex="0"
title={tooltipText}
className="dashicons dashicons-undo delete-pointer"
className={iconClass}
onClick={(event) => {
event.preventDefault();
this.removePointer(item);
Expand All @@ -456,7 +474,15 @@ export class Pointers extends Component {
}}
>
<span className="screen-reader-text">
Remove Post
{isCustomResult
? __(
'Remove custom result',
'elasticpress',
)
: __(
'Return to original position',
'elasticpress',
)}
</span>
</span>
)}
Expand Down
83 changes: 73 additions & 10 deletions includes/classes/Feature/SearchOrdering/SearchOrdering.php
Original file line number Diff line number Diff line change
Expand Up @@ -372,8 +372,9 @@ public function get_pointer_data_for_localize() {

if ( empty( $pointers ) ) {
return [
'pointers' => [],
'posts' => [],
'pointers' => [],
'posts' => [],
'excluded_posts' => [],
];
}

Expand All @@ -397,9 +398,12 @@ public function get_pointer_data_for_localize() {
$filtered_pointers[] = $pointers[ array_search( $post->ID, $post_ids, true ) ];
}

$excluded_posts = get_post_meta( $post_id, 'excluded_posts', true );

return [
'pointers' => $filtered_pointers,
'posts' => $final_posts,
'pointers' => $filtered_pointers,
'posts' => $final_posts,
'excluded_posts' => ! empty( $excluded_posts ) ? $excluded_posts : [],
];
}

Expand Down Expand Up @@ -480,7 +484,7 @@ public function save_post( $post_id, $post ) {

// Search term changed, so remove it from all of the posts it was assigned to
if ( ! empty( $old_search_term ) && $old_search_term !== $post->post_title ) {
$old_term = $this->create_or_return_custom_result_term( $old_search_term );
$old_term = $this->create_or_return_custom_result_term( $old_search_term, $post_id );

foreach ( array_flip( $previous_post_ids ) as $previous_post_id ) {
wp_remove_object_terms( $previous_post_id, $old_term->term_id, self::TAXONOMY_NAME );
Expand All @@ -505,7 +509,7 @@ public function save_post( $post_id, $post ) {
}
}

$custom_result_term = $this->create_or_return_custom_result_term( $post->post_title );
$custom_result_term = $this->create_or_return_custom_result_term( $post->post_title, $post_id );
if ( $custom_result_term ) {
foreach ( $final_order_data as $final_order_datum ) {

Expand All @@ -529,18 +533,29 @@ public function save_post( $post_id, $post ) {
}
}

$excluded_posts = isset( $_POST['excluded_posts'] ) ? wp_unslash( $_POST['excluded_posts'] ) : '[]'; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
$excluded_posts = json_decode( $excluded_posts, true );
$excluded_posts = ! empty( $excluded_posts ) && is_array( $excluded_posts ) ? array_map( 'intval', $excluded_posts ) : [];

// If the search term changed, clear the excluded list
if ( ! empty( $old_search_term ) && $old_search_term !== $post->post_title ) {
$excluded_posts = [];
}

update_post_meta( $post_id, 'pointers', $final_order_data );
update_post_meta( $post_id, 'excluded_posts', $excluded_posts );
update_post_meta( $post_id, 'search_term', $post->post_title );
}

/**
* Creates a term in the taxonomy for tracking ordered results or returns the existing term
*
* @param string $term_name Term name to fetch or create
* @param string $term_name Term name to fetch or create
* @param int|null $pointer_id Optional. Pointer post ID to associate with the term.
*
* @return false|\WP_Term
*/
public function create_or_return_custom_result_term( $term_name ) {
public function create_or_return_custom_result_term( $term_name, $pointer_id = null ) {
$term = get_term_by( 'name', $term_name, self::TAXONOMY_NAME );

if ( ! $term ) {
Expand All @@ -553,6 +568,10 @@ public function create_or_return_custom_result_term( $term_name ) {
$term = get_term( $term_ids['term_id'], self::TAXONOMY_NAME );
}

if ( $term && ! empty( $pointer_id ) ) {
update_term_meta( $term->term_id, 'ep_pointer_id', $pointer_id );
}

return $term;
}

Expand Down Expand Up @@ -651,15 +670,19 @@ public function filter_column_names( $columns ) {
public function posts_results( $posts, $query ) {
if ( is_array( $posts ) && $query->is_search() ) {
$search_query = strtolower( $query->get( 's' ) );

$to_inject = array();
$to_inject = array();
$pointer_id = false;

foreach ( $posts as $key => &$post ) {
if ( isset( $post->terms ) && isset( $post->terms[ self::TAXONOMY_NAME ] ) ) {
foreach ( $post->terms[ self::TAXONOMY_NAME ] as $current_term ) {
if ( strtolower( $current_term['name'] ) === $search_query ) {
$to_inject[ $current_term['term_order'] ] = $post;

if ( empty( $pointer_id ) && ! empty( $current_term['term_id'] ) ) {
$pointer_id = get_term_meta( (int) $current_term['term_id'], 'ep_pointer_id', true );
}

unset( $posts[ $key ] );

break;
Expand All @@ -682,11 +705,51 @@ public function posts_results( $posts, $query ) {

// reindex just in case we got out of order keys
$posts = array_values( $posts );

$posts = $this->filter_excluded_posts( $posts, $pointer_id );
}

return $posts;
}

/**
* Filters out posts that have been explicitly excluded from a custom search result.
*
* @param array $posts Current array of post results.
* @param int $pointer_id ID of the custom search result pointer post.
* @return array Final modified posts array.
*/
public function filter_excluded_posts( $posts, $pointer_id ) {
static $excluded_cache = array();

if ( ! isset( $excluded_cache[ $pointer_id ] ) ) {
$excluded_cache[ $pointer_id ] = array();

if ( ! empty( $pointer_id ) ) {
$excluded = get_post_meta( $pointer_id, 'excluded_posts', true );

if ( ! empty( $excluded ) && is_array( $excluded ) ) {
$excluded_cache[ $pointer_id ] = array_map( 'intval', $excluded );
}
}
}

$excluded_posts = $excluded_cache[ $pointer_id ];

if ( empty( $excluded_posts ) ) {
return $posts;
}

$posts = array_filter(
$posts,
function ( $post ) use ( $excluded_posts ) {
return ! in_array( (int) $post->ID, $excluded_posts, true );
}
);

return array_values( $posts );
}

/**
* Registers the API endpoint for searching from the admin interface
*/
Expand Down
Loading