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
3 changes: 3 additions & 0 deletions assets/js/ajax-filters.js
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,7 @@ jQuery( document ).ready( function( $ ) {
var remote_position = $target.data( 'remote_position' );
var job_types = $target.data( 'job_types' );
var post_status = $target.data( 'post_status' );
var author = $target.data( 'author' );
var index = $( 'div.job_listings' ).index( this );
var categories, keywords, location;

Expand Down Expand Up @@ -387,6 +388,7 @@ jQuery( document ).ready( function( $ ) {
featured: featured,
filled: filled,
remote_position: remote_position,
author: author,
show_pagination: $target.data( 'show_pagination' ),
form_data: $form.serialize(),
};
Expand Down Expand Up @@ -416,6 +418,7 @@ jQuery( document ).ready( function( $ ) {
featured: featured,
filled: filled,
remote_position: remote_position,
author: author,
show_pagination: $target.data( 'show_pagination' ),
};
}
Expand Down
3 changes: 3 additions & 0 deletions includes/class-wp-job-manager-ajax.php
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ public function get_listings() {
$remote_position = isset( $_REQUEST['remote_position'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['remote_position'] ) ) : null;
$show_pagination = isset( $_REQUEST['show_pagination'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['show_pagination'] ) ) : null;
$featured_first = isset( $_REQUEST['featured_first'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['featured_first'] ) ) : null;
$author = ( isset( $_REQUEST['author'] ) && ! is_array( $_REQUEST['author'] ) ) ? sanitize_text_field( wp_unslash( $_REQUEST['author'] ) ) : '';
// phpcs:enable WordPress.Security.NonceVerification.Recommended

if ( is_array( $search_categories ) ) {
Expand Down Expand Up @@ -176,6 +177,7 @@ public function get_listings() {
'orderby' => $orderby,
'order' => $order,
'featured_first' => $featured_first,
'author' => $author,
'offset' => ( $page - 1 ) * $per_page,
'posts_per_page' => max( 1, $per_page ), // phpcs:ignore WordPress.WP.PostsPerPage.posts_per_page_posts_per_page -- Known slow query.
];
Expand Down Expand Up @@ -245,6 +247,7 @@ public function get_listings() {
'search_location' => $search_location,
'search_categories' => $search_categories,
'search_keywords' => $search_keywords,
'author' => $author,
]
);

Expand Down
15 changes: 15 additions & 0 deletions includes/class-wp-job-manager-post-types.php
Original file line number Diff line number Diff line change
Expand Up @@ -744,6 +744,17 @@ public function job_feed() {
$input_job_categories = false;
}

if ( isset( $_GET['author'] ) && ! is_array( $_GET['author'] ) ) {
$sanitized_author = sanitize_text_field( wp_unslash( $_GET['author'] ) );
$input_author = empty( $sanitized_author ) ? false : array_values( array_filter( array_map( 'intval', explode( ',', $sanitized_author ) ), fn( $v ) => $v > 0 ) );
// Fails-closed: author was supplied but yielded no valid IDs → force empty results.
if ( false !== $input_author && empty( $input_author ) ) {
$input_author = [ 0 ];
}
} else {
$input_author = false;
}

$job_manager_keyword = isset( $_GET['search_keywords'] ) ? sanitize_text_field( wp_unslash( $_GET['search_keywords'] ) ) : '';
$input_featured = isset( $_GET['featured'] ) ? sanitize_text_field( wp_unslash( $_GET['featured'] ) ) : null;
// phpcs:enable WordPress.Security.NonceVerification.Recommended
Expand Down Expand Up @@ -816,6 +827,10 @@ public function job_feed() {
];
}

if ( ! empty( $input_author ) ) {
$query_args['author__in'] = $input_author;
}

if ( ! empty( $job_manager_keyword ) ) {
$query_args['s'] = $job_manager_keyword;
add_filter( 'posts_search', 'get_job_listings_keyword_search', 10, 2 );
Expand Down
6 changes: 6 additions & 0 deletions includes/class-wp-job-manager-shortcodes.php
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ public function output_jobs( $atts ) {
'filled' => null, // True to show only filled, false to hide filled, leave null to show both/use the settings.
'remote_position' => null, // True to show only remote, false to hide remote, leave null to show both.
'featured_first' => false, // True to show featured first, false to show in default order.
'author' => 0, // Limit listings to a specific author by user ID. 0 shows all.

// Default values for filters.
'location' => '',
Expand All @@ -237,6 +238,7 @@ public function output_jobs( $atts ) {
$atts['show_more'] = $this->string_to_bool( $atts['show_more'] );
$atts['show_pagination'] = $this->string_to_bool( $atts['show_pagination'] );
$atts['featured_first'] = $this->string_to_bool( $atts['featured_first'] );
$atts['author'] = sanitize_text_field( $atts['author'] );

if ( ! is_null( $atts['featured'] ) ) {
$atts['featured'] = ( is_bool( $atts['featured'] ) && $atts['featured'] ) || in_array( $atts['featured'], [ 1, '1', 'true', 'yes' ], true );
Expand Down Expand Up @@ -349,6 +351,7 @@ public function output_jobs( $atts ) {
'filled' => $atts['filled'],
'remote_position' => $atts['remote_position'],
'featured_first' => $atts['featured_first'],
'author' => $atts['author'],
]
)
);
Expand Down Expand Up @@ -392,6 +395,9 @@ public function output_jobs( $atts ) {
if ( ! empty( $atts['post_status'] ) ) {
$data_attributes['post_status'] = implode( ',', $atts['post_status'] );
}
if ( ! empty( $atts['author'] ) ) {
$data_attributes['author'] = $atts['author'];
}

$data_attributes['post_id'] = isset( $GLOBALS['post'] ) ? $GLOBALS['post']->ID : 0;

Expand Down
130 changes: 130 additions & 0 deletions tests/php/tests/test_class.wp-job-manager-functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -980,4 +980,134 @@ public function test_renew_job_listing() {
WP_Job_Manager_Helper_Renewals::renew_job_listing( get_post( $job_listing_id ) );
$this->assertFalse( WP_Job_Manager_Helper_Renewals::job_can_be_renewed( $job_listing ) );
}

/**
* @since 2.5.0
* @covers ::get_job_listings
*/
public function test_get_job_listings_author_single_id() {
$user_a = $this->factory->user->create();
$user_b = $this->factory->user->create();

$jobs_a = $this->factory->job_listing->create_many( 2, [ 'post_author' => $user_a ] );
$jobs_b = $this->factory->job_listing->create_many( 3, [ 'post_author' => $user_b ] );

$result = get_job_listings( [ 'author' => (string) $user_a ] );

$this->assertEqualSets( $jobs_a, wp_list_pluck( $result->posts, 'ID' ) );
}

/**
* @since 2.5.0
* @covers ::get_job_listings
*/
public function test_get_job_listings_author_multiple_ids() {
$user_a = $this->factory->user->create();
$user_b = $this->factory->user->create();
$user_c = $this->factory->user->create();

$jobs_a = $this->factory->job_listing->create_many( 2, [ 'post_author' => $user_a ] );
$jobs_b = $this->factory->job_listing->create_many( 2, [ 'post_author' => $user_b ] );
$jobs_c = $this->factory->job_listing->create_many( 2, [ 'post_author' => $user_c ] );

$result = get_job_listings( [ 'author' => $user_a . ',' . $user_b ] );

$this->assertEqualSets( array_merge( $jobs_a, $jobs_b ), wp_list_pluck( $result->posts, 'ID' ) );
}

/**
* @since 2.5.0
* @covers ::get_job_listings
*/
public function test_get_job_listings_author_empty_string_shows_no_filter() {
$user_a = $this->factory->user->create();
$this->factory->job_listing->create_many( 2, [ 'post_author' => $user_a ] );

$result = get_job_listings( [ 'author' => '' ] );

// Empty string means no filter — all listings are returned.
$this->assertGreaterThanOrEqual( 2, $result->found_posts );
}

/**
* Non-numeric input should return zero results (fails-closed).
*
* @since 2.5.0
* @covers ::get_job_listings
*/
public function test_get_job_listings_author_non_numeric_returns_no_results() {
$user_a = $this->factory->user->create();
$this->factory->job_listing->create_many( 2, [ 'post_author' => $user_a ] );

$result = get_job_listings( [ 'author' => 'abc' ] );

$this->assertSame( 0, $result->found_posts );
}

/**
* Negative IDs should not be converted to positive via absint and must return zero results.
*
* @since 2.5.0
* @covers ::get_job_listings
*/
public function test_get_job_listings_author_negative_id_returns_no_results() {
$user_a = $this->factory->user->create();
$this->factory->job_listing->create_many( 2, [ 'post_author' => $user_a ] );

$result = get_job_listings( [ 'author' => '-5' ] );

$this->assertSame( 0, $result->found_posts );
}

/**
* @since 2.5.0
* @covers ::get_job_listings
*/
public function test_get_job_listings_author_zero_returns_no_results() {
$user_a = $this->factory->user->create();
$this->factory->job_listing->create_many( 2, [ 'post_author' => $user_a ] );

$result = get_job_listings( [ 'author' => '0' ] );

$this->assertSame( 0, $result->found_posts );
}

/**
* Mixed valid and invalid IDs: only valid IDs should be used.
*
* @since 2.5.0
* @covers ::get_job_listings
*/
public function test_get_job_listings_author_mixed_valid_and_invalid() {
$user_a = $this->factory->user->create();
$user_b = $this->factory->user->create();

$jobs_a = $this->factory->job_listing->create_many( 2, [ 'post_author' => $user_a ] );
$this->factory->job_listing->create_many( 2, [ 'post_author' => $user_b ] );

// 'abc' parses to 0 and should be dropped; only $user_a's listings are returned.
$result = get_job_listings( [ 'author' => $user_a . ',abc' ] );

$this->assertEqualSets( $jobs_a, wp_list_pluck( $result->posts, 'ID' ) );
}

/**
* Array input with valid user IDs should work.
*
* @since 2.5.0
* @covers ::get_job_listings
*/
public function test_get_job_listings_author_array_input() {
$user_a = $this->factory->user->create();
$user_b = $this->factory->user->create();
$user_c = $this->factory->user->create();

$jobs_a = $this->factory->job_listing->create_many( 2, [ 'post_author' => $user_a ] );
$jobs_b = $this->factory->job_listing->create_many( 2, [ 'post_author' => $user_b ] );
$this->factory->job_listing->create_many( 2, [ 'post_author' => $user_c ] );

$result = get_job_listings( [ 'author' => [ $user_a, $user_b ] ] );

$this->assertEqualSets( array_merge( $jobs_a, $jobs_b ), wp_list_pluck( $result->posts, 'ID' ) );
}
}
16 changes: 15 additions & 1 deletion wp-job-manager-functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@
* Queries job listings with certain criteria and returns them.
*
* @since 1.0.5
* @param string|array|object $args Arguments used to retrieve job listings.
* @param string|array|object $args {
* Arguments used to retrieve job listings.
*
* @type int|string|int[] $author Optional. User ID, comma-separated user IDs, or array of user IDs to filter listings by author. Default 0 (no filter).
* }
* @return WP_Query
*/
function get_job_listings( $args = [] ) {
Expand Down Expand Up @@ -193,6 +197,15 @@ function get_job_listings( $args = [] ) {
];
}

if ( isset( $args['author'] ) && ( is_array( $args['author'] ) || '' !== $args['author'] ) ) {
$raw_author = $args['author'];
$author_ids = is_array( $raw_author )
? array_values( array_filter( array_map( 'intval', $raw_author ), fn( $v ) => $v > 0 ) )
: array_values( array_filter( array_map( 'intval', explode( ',', (string) $raw_author ) ), fn( $v ) => $v > 0 ) );
// Fails-closed: if author was supplied but yielded no valid IDs, return zero results.
$query_args['author__in'] = ! empty( $author_ids ) ? $author_ids : [ 0 ];
}

$job_manager_keyword = sanitize_text_field( $args['search_keywords'] );

if ( ! empty( $job_manager_keyword ) && strlen( $job_manager_keyword ) >= apply_filters( 'job_manager_get_listings_keyword_length_threshold', 2 ) ) {
Expand Down Expand Up @@ -629,6 +642,7 @@ function job_manager_get_filtered_links( $args = [] ) {
'search_location' => $args['search_location'],
'job_categories' => implode( ',', $job_categories ),
'search_keywords' => $args['search_keywords'],
'author' => ! empty( $args['author'] ) ? $args['author'] : '',
]
)
),
Expand Down
Loading