PluginProbe ʕ •ᴥ•ʔ
WP Job Manager / 2.4.7
WP Job Manager v2.4.7
2.4.7 2.4.6 2.4.5 2.4.4 2.4.3 2.4.2 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.1.0 1.1.1 1.1.2 1.1.3 1.10.0 1.11.0 1.11.1 1.12.0 1.12.1 1.13.0 1.14.0 1.15.0 1.16.0 1.16.1 1.17.0 1.18.0 1.19.0 1.2.0 1.20.0 1.20.1 1.21.0 1.21.1 1.21.2 1.21.3 1.21.4 1.22.0 1.22.1 1.22.2 1.22.3 1.23.0 1.23.1 1.23.10 1.23.11 1.23.12 1.23.13 1.23.2 1.23.3 1.23.4 1.23.5 1.23.6 1.23.7 1.23.8 1.23.9 1.24.0 1.24.0.1 1.25.0 1.25.0.1 1.25.1 1.25.1.1 1.25.2 1.25.2.1 1.25.3 1.25.3.1 1.26.0 1.26.0.1 1.26.1 1.26.1.1 1.26.2 1.26.2.1 1.27.0 1.27.0.1 1.28.0 1.28.0.1 1.29.0 1.29.0.1 1.29.1 1.29.1.1 1.29.2 1.29.2.1 1.29.3 1.29.3.1 1.3.0 1.3.1 1.30.0 1.30.0.1 1.30.1 1.30.1.1 1.30.2 1.30.2.1 1.31.0 1.31.0.1 1.31.1 1.31.1.1 1.31.2 1.31.3 1.32.0 1.32.1 1.32.2 1.32.3 1.33.0 1.33.1 1.33.2 1.33.3 1.33.4 1.33.5 1.34.0 1.34.1 1.34.2 1.34.3 1.34.4 1.34.5 1.35.0 1.35.1 1.35.2 1.35.3 1.36.0 1.36.1 1.36.2 1.37.0 1.38.0 1.38.1 1.39.0 1.4.0 1.40.0 1.40.1 1.40.2 1.41.0 1.42.0 1.5.0 1.5.1 1.5.2 1.6.0 1.7.0 1.7.1 1.7.3 1.8.0 1.8.1 1.8.2 1.9.0 1.9.1 1.9.2 1.9.3 2.0.0 2.1.0 2.1.1 2.2.0 2.2.1 2.2.2 2.3.0 2.4.0 2.4.1
wp-job-manager / includes / class-wp-job-manager-ajax.php
wp-job-manager / includes Last commit date
3rd-party 4 days ago abstracts 3 months ago admin 2 weeks ago emails 2 weeks ago forms 4 days ago helper 3 months ago promoted-jobs 4 days ago ui 2 weeks ago widgets 2 weeks ago class-access-token.php 2 years ago class-dev-tools.php 2 years ago class-guest-session.php 2 years ago class-guest-user.php 2 years ago class-job-dashboard-shortcode.php 6 months ago class-job-listing-stats.php 2 years ago class-job-overlay.php 2 years ago class-stats-dashboard.php 2 years ago class-stats-script.php 3 months ago class-stats.php 3 months ago class-wp-job-manager-ajax.php 2 months ago class-wp-job-manager-api.php 6 years ago class-wp-job-manager-blocks.php 2 weeks ago class-wp-job-manager-cache-helper.php 3 months ago class-wp-job-manager-category-walker.php 6 years ago class-wp-job-manager-com-api.php 2 years ago class-wp-job-manager-data-cleaner.php 2 years ago class-wp-job-manager-data-exporter.php 3 months ago class-wp-job-manager-dependency-checker.php 2 years ago class-wp-job-manager-email-notifications.php 2 years ago class-wp-job-manager-forms.php 5 years ago class-wp-job-manager-geocode.php 2 weeks ago class-wp-job-manager-install.php 2 years ago class-wp-job-manager-post-types.php 4 days ago class-wp-job-manager-recaptcha.php 6 months ago class-wp-job-manager-rest-api.php 2 months ago class-wp-job-manager-shortcodes.php 2 weeks ago class-wp-job-manager-usage-tracking-data.php 2 years ago class-wp-job-manager-usage-tracking.php 2 years ago class-wp-job-manager-widget.php 2 weeks ago class-wp-job-manager.php 3 months ago trait-singleton.php 2 years ago
class-wp-job-manager-ajax.php
502 lines
1 <?php
2 /**
3 * File containing the class WP_Job_Manager_Ajax.
4 *
5 * @package wp-job-manager
6 */
7
8 if ( ! defined( 'ABSPATH' ) ) {
9 exit;
10 }
11
12 /**
13 * Handles Job Manager's Ajax endpoints.
14 *
15 * @since 1.0.0
16 */
17 class WP_Job_Manager_Ajax {
18
19 /**
20 * The single instance of the class.
21 *
22 * @var self
23 * @since 1.26.0
24 */
25 private static $instance = null;
26
27 /**
28 * Allows for accessing single instance of class. Class should only be constructed once per call.
29 *
30 * @since 1.26.0
31 * @static
32 * @return self Main instance.
33 */
34 public static function instance() {
35 if ( is_null( self::$instance ) ) {
36 self::$instance = new self();
37 }
38 return self::$instance;
39 }
40
41 /**
42 * Constructor.
43 */
44 public function __construct() {
45 add_action( 'init', [ __CLASS__, 'add_endpoint' ] );
46 add_action( 'template_redirect', [ __CLASS__, 'do_jm_ajax' ], 0 );
47
48 // JM Ajax endpoints.
49 add_action( 'job_manager_ajax_get_listings', [ $this, 'get_listings' ] );
50 add_action( 'job_manager_ajax_upload_file', [ $this, 'upload_file' ] );
51
52 // BW compatible handlers.
53 add_action( 'wp_ajax_nopriv_job_manager_get_listings', [ $this, 'get_listings' ] );
54 add_action( 'wp_ajax_job_manager_get_listings', [ $this, 'get_listings' ] );
55 add_action( 'wp_ajax_nopriv_job_manager_upload_file', [ $this, 'upload_file' ] );
56 add_action( 'wp_ajax_job_manager_upload_file', [ $this, 'upload_file' ] );
57 add_action( 'wp_ajax_job_manager_search_users', [ $this, 'ajax_search_users' ] );
58 }
59
60 /**
61 * Adds endpoint for frontend Ajax requests.
62 */
63 public static function add_endpoint() {
64 add_rewrite_tag( '%jm-ajax%', '([^/]*)' );
65 add_rewrite_rule( 'jm-ajax/([^/]*)/?', 'index.php?jm-ajax=$matches[1]', 'top' );
66 add_rewrite_rule( 'index.php/jm-ajax/([^/]*)/?', 'index.php?jm-ajax=$matches[1]', 'top' );
67 }
68
69 /**
70 * Gets Job Manager's Ajax Endpoint.
71 *
72 * @param string $request Optional.
73 * @return string
74 */
75 public static function get_endpoint( $request = '%%endpoint%%' ) {
76 if ( strstr( get_option( 'permalink_structure' ), '/index.php/' ) ) {
77 $endpoint = trailingslashit( home_url( '/index.php/jm-ajax/' . $request . '/', 'relative' ) );
78 } elseif ( get_option( 'permalink_structure' ) ) {
79 $endpoint = trailingslashit( home_url( '/jm-ajax/' . $request . '/', 'relative' ) );
80 } else {
81 $endpoint = add_query_arg( 'jm-ajax', $request, home_url( '/', 'relative' ) );
82 }
83
84 return esc_url_raw( $endpoint );
85 }
86
87 /**
88 * Performs Job Manager's Ajax actions.
89 */
90 public static function do_jm_ajax() {
91 global $wp_query;
92
93 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Input is used safely.
94 if ( ! empty( $_GET['jm-ajax'] ) ) {
95 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Input is used safely.
96 $wp_query->set( 'jm-ajax', sanitize_text_field( wp_unslash( $_GET['jm-ajax'] ) ) );
97 }
98
99 $action = $wp_query->get( 'jm-ajax' );
100 if ( $action ) {
101 if ( ! defined( 'DOING_AJAX' ) ) {
102 define( 'DOING_AJAX', true );
103 }
104
105 // Not home - this is an ajax endpoint.
106 $wp_query->is_home = false;
107
108 /**
109 * Performs an Ajax action.
110 * The dynamic part of the action, $action, is the predefined Ajax action to be performed.
111 *
112 * @since 1.23.0
113 */
114 do_action( 'job_manager_ajax_' . sanitize_text_field( $action ) );
115 wp_die();
116 }
117 }
118
119 /**
120 * Returns Job Listings for Ajax endpoint.
121 */
122 public function get_listings() {
123 // Get input variables.
124 // phpcs:disable WordPress.Security.NonceVerification.Recommended -- Fetching data only; often for logged out visitors.
125 $search_location = isset( $_REQUEST['search_location'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['search_location'] ) ) : '';
126 $search_keywords = isset( $_REQUEST['search_keywords'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['search_keywords'] ) ) : '';
127 $search_categories = isset( $_REQUEST['search_categories'] ) ? wp_unslash( $_REQUEST['search_categories'] ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Input is sanitized below.
128 $filter_job_types = isset( $_REQUEST['filter_job_type'] ) ? array_filter( array_map( 'sanitize_title', wp_unslash( (array) $_REQUEST['filter_job_type'] ) ) ) : null;
129 $filter_post_status = isset( $_REQUEST['filter_post_status'] ) ? array_filter( array_map( 'sanitize_title', wp_unslash( (array) $_REQUEST['filter_post_status'] ) ) ) : null;
130 $order = isset( $_REQUEST['order'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['order'] ) ) : 'DESC';
131 $orderby = isset( $_REQUEST['orderby'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['orderby'] ) ) : 'featured';
132 $page = isset( $_REQUEST['page'] ) ? absint( $_REQUEST['page'] ) : 1;
133 $per_page = isset( $_REQUEST['per_page'] ) ? absint( $_REQUEST['per_page'] ) : absint( get_option( 'job_manager_per_page' ) );
134 $filled = isset( $_REQUEST['filled'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['filled'] ) ) : null;
135 $featured = isset( $_REQUEST['featured'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['featured'] ) ) : null;
136 $remote_position = isset( $_REQUEST['remote_position'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['remote_position'] ) ) : null;
137 $show_pagination = isset( $_REQUEST['show_pagination'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['show_pagination'] ) ) : null;
138 $featured_first = isset( $_REQUEST['featured_first'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['featured_first'] ) ) : null;
139 if ( ! isset( $_REQUEST['author'] ) ) {
140 $author = '';
141 } elseif ( is_array( $_REQUEST['author'] ) ) {
142 // Array-shaped input is not supported via AJAX - fails closed (passes '0' so get_job_listings returns zero results).
143 $author = '0';
144 } else {
145 $author = sanitize_text_field( wp_unslash( $_REQUEST['author'] ) );
146 }
147 // phpcs:enable WordPress.Security.NonceVerification.Recommended
148
149 if ( is_array( $search_categories ) ) {
150 $search_categories = array_filter( array_map( 'sanitize_text_field', array_map( 'stripslashes', $search_categories ) ) );
151 } else {
152 $search_categories = array_filter( [ sanitize_text_field( wp_unslash( $search_categories ) ) ] );
153 }
154
155 // Ensure the current user can filter by post_status. Users without the capability may only
156 // narrow the query to publicly visible listings (e.g. the `[jobs post_status="publish"]`
157 // shortcode), never request unpublished statuses such as draft, pending, preview or private.
158 if ( is_array( $filter_post_status ) && ! current_user_can( \WP_Job_Manager_Post_Types::CAP_EDIT_LISTINGS ) ) {
159 /**
160 * Post statuses a visitor without listing-editing capabilities is allowed to filter by.
161 *
162 * @since 2.4.5
163 *
164 * @param string[] $allowed_post_status Allowed post statuses. Defaults to `[ 'publish' ]`.
165 */
166 $allowed_post_status = apply_filters( 'job_manager_get_listings_public_post_status', [ 'publish' ] );
167 $filter_post_status = array_values( array_intersect( $filter_post_status, $allowed_post_status ) );
168
169 if ( empty( $filter_post_status ) ) {
170 $filter_post_status = null;
171 }
172 }
173
174 // Browse-capability gate — match the [jobs] shortcode denial without surfacing partial results.
175 if ( ! job_manager_user_can_browse_job_listings() ) {
176 wp_send_json(
177 [
178 'found_jobs' => false,
179 'showing' => '',
180 'showing_all' => false,
181 'showing_links' => '',
182 'max_num_pages' => 0,
183 'html' => '',
184 ]
185 );
186 return;
187 }
188
189 $types = get_job_listing_types();
190 $job_types_filtered = ! is_null( $filter_job_types ) && count( $types ) !== count( $filter_job_types );
191
192 $args = [
193 'search_location' => $search_location,
194 'search_keywords' => $search_keywords,
195 'search_categories' => $search_categories,
196 'job_types' => is_null( $filter_job_types ) || count( $types ) === count( $filter_job_types ) ? '' : $filter_job_types + [ 0 ],
197 'post_status' => $filter_post_status,
198 'orderby' => $orderby,
199 'order' => $order,
200 'featured_first' => $featured_first,
201 'author' => $author,
202 'offset' => ( $page - 1 ) * $per_page,
203 'posts_per_page' => max( 1, $per_page ), // phpcs:ignore WordPress.WP.PostsPerPage.posts_per_page_posts_per_page -- Known slow query.
204 ];
205
206 if ( 'true' === $filled || 'false' === $filled ) {
207 $args['filled'] = 'true' === $filled;
208 }
209
210 if ( 'true' === $remote_position || 'false' === $remote_position ) {
211 $args['remote_position'] = 'true' === $remote_position;
212 }
213
214 if ( 'true' === $featured || 'false' === $featured ) {
215 $args['featured'] = 'true' === $featured;
216 $args['orderby'] = 'featured' === $orderby ? 'date' : $orderby;
217 }
218
219 /**
220 * Get the arguments to use when building the Job Listing WP Query.
221 *
222 * @since 1.0.0
223 *
224 * @param array $args Arguments used for generating Job Listing query (see `get_job_listings()`).
225 */
226 $jobs = get_job_listings( apply_filters( 'job_manager_get_listings_args', $args ) );
227
228 $result = [
229 'found_jobs' => $jobs->have_posts(),
230 'showing' => '',
231 'max_num_pages' => $jobs->max_num_pages,
232 ];
233
234 if ( ( $search_location || $search_keywords || $search_categories || $job_types_filtered ) ) {
235 // translators: Placeholder %d is the number of found search results.
236 $message = sprintf( _n( 'Search completed. Found %d matching record.', 'Search completed. Found %d matching records.', $jobs->found_posts, 'wp-job-manager' ), $jobs->found_posts );
237 $result['showing_all'] = true;
238 } else {
239 $message = '';
240 }
241
242 $search_values = [
243 'location' => $search_location,
244 'keywords' => $search_keywords,
245 'categories' => $search_categories,
246 ];
247
248 /**
249 * Filter the message that describes the results of the search query.
250 *
251 * @since 1.0.0
252 *
253 * @param string $message Default message that is generated when posts are found.
254 * @param array $search_values {
255 * Helpful values often used in the generation of this message.
256 *
257 * @type string $location Query used to filter by job listing location.
258 * @type string $keywords Query used to filter by general keywords.
259 * @type array $categories List of the categories to filter by.
260 * }
261 */
262 $result['showing'] = apply_filters( 'job_manager_get_listings_custom_filter_text', $message, $search_values );
263
264 // Generate RSS link.
265 $result['showing_links'] = job_manager_get_filtered_links(
266 [
267 'filter_job_types' => $filter_job_types,
268 'search_location' => $search_location,
269 'search_categories' => $search_categories,
270 'search_keywords' => $search_keywords,
271 'author' => $author,
272 ]
273 );
274
275 /**
276 * Send back a response to the AJAX request without creating HTML.
277 *
278 * @since 1.26.0
279 *
280 * @param array $result
281 * @param WP_Query $jobs
282 * @return bool True by default. Change to false to halt further response.
283 */
284 if ( true !== apply_filters( 'job_manager_ajax_get_jobs_html_results', true, $result, $jobs ) ) {
285 /**
286 * Filters the results of the job listing Ajax query to be sent back to the client.
287 *
288 * @since 1.0.0
289 *
290 * @param array $result {
291 * Package of the query results along with meta information.
292 *
293 * @type bool $found_jobs Whether or not jobs were found in the query.
294 * @type string $showing Description of the search query and results.
295 * @type int $max_num_pages Number of pages in the search result.
296 * @type string $html HTML representation of the search results (only if filter
297 * `job_manager_ajax_get_jobs_html_results` returns true).
298 * @type array $pagination Pagination links to use for stepping through filter results.
299 * }
300 */
301 wp_send_json( apply_filters( 'job_manager_get_listings_result', $result, $jobs ) );
302
303 return;
304 }
305
306 ob_start();
307
308 if ( $result['found_jobs'] ) {
309 while ( $jobs->have_posts() ) {
310 $jobs->the_post();
311 get_job_manager_template_part( 'content', \WP_Job_Manager_Post_Types::PT_LISTING );
312 }
313 } else {
314 get_job_manager_template_part( 'content', 'no-jobs-found' );
315 }
316
317 $result['html'] = ob_get_clean();
318
319 // Generate pagination.
320 if ( 'true' === $show_pagination ) {
321 $result['pagination'] = get_job_listing_pagination( $jobs->max_num_pages, $page );
322 }
323
324 /** This filter is documented in includes/class-wp-job-manager-ajax.php (above) */
325 wp_send_json( apply_filters( 'job_manager_get_listings_result', $result, $jobs ) );
326 }
327
328 /**
329 * Uploads file from an Ajax request.
330 *
331 * No nonce field since the form may be statically cached.
332 */
333 public function upload_file() {
334 if ( ! job_manager_user_can_upload_file_via_ajax() ) {
335 wp_send_json_error( __( 'You must be logged in to upload files using this method.', 'wp-job-manager' ) );
336 return;
337 }
338 $data = [
339 'files' => [],
340 ];
341
342 if ( ! empty( $_FILES ) ) {
343 foreach ( $_FILES as $file_key => $file ) {
344 $files_to_upload = job_manager_prepare_uploaded_files( $file );
345 foreach ( $files_to_upload as $file_to_upload ) {
346 $uploaded_file = job_manager_upload_file(
347 $file_to_upload,
348 [
349 'file_key' => $file_key,
350 ]
351 );
352
353 if ( is_wp_error( $uploaded_file ) ) {
354 $data['files'][] = [
355 'error' => $uploaded_file->get_error_message(),
356 ];
357 } else {
358 $data['files'][] = $uploaded_file;
359 }
360 }
361 }
362 }
363
364 wp_send_json( $data );
365 }
366
367 /**
368 * Checks if user can search for other users in ajax call.
369 *
370 * @return bool
371 */
372 private static function user_can_search_users() {
373 $user_can_search_users = false;
374
375 /**
376 * Filter the capabilities that are allowed to search for users in ajax call.
377 *
378 * @since 1.32.0
379 *
380 * @param array $user_caps Array of capabilities/roles that are allowed to search for users.
381 */
382 $allowed_capabilities = apply_filters( 'job_manager_caps_can_search_users', [ \WP_Job_Manager_Post_Types::CAP_EDIT_LISTINGS ] );
383 foreach ( $allowed_capabilities as $cap ) {
384 if ( current_user_can( $cap ) ) {
385 $user_can_search_users = true;
386 break;
387 }
388 }
389
390 /**
391 * Filters whether the current user can search for users in ajax call.
392 *
393 * @since 1.32.0
394 *
395 * @param bool $user_can_search_users True if they are allowed, false if not.
396 */
397 return apply_filters( 'job_manager_user_can_search_users', $user_can_search_users );
398 }
399
400 /**
401 * Search for users and return json.
402 */
403 public static function ajax_search_users() {
404 check_ajax_referer( 'search-users', 'security' );
405
406 if ( ! self::user_can_search_users() ) {
407 wp_die( -1 );
408 }
409
410 $term = isset( $_GET['term'] ) ? sanitize_text_field( wp_unslash( $_GET['term'] ) ) : '';
411 $page = isset( $_GET['page'] ) ? intval( $_GET['page'] ) : 1;
412 $per_page = 20;
413
414 $exclude = [];
415 if ( ! empty( $_GET['exclude'] ) ) {
416 $exclude = array_map( 'intval', $_GET['exclude'] );
417 }
418
419 if ( empty( $term ) ) {
420 wp_die();
421 }
422
423 $more_exist = false;
424 $users = [];
425
426 // Search by ID.
427 if ( is_numeric( $term ) && ! in_array( intval( $term ), $exclude, true ) ) {
428 $user = get_user_by( 'ID', intval( $term ) );
429 if ( $user instanceof WP_User ) {
430 $users[ $user->ID ] = $user;
431 }
432 }
433
434 if ( empty( $users ) ) {
435 $search_args = [
436 'exclude' => $exclude,
437 'search' => '*' . esc_attr( $term ) . '*',
438 'search_columns' => [ 'user_login', 'user_email', 'user_nicename', 'display_name' ],
439 'number' => $per_page,
440 'paged' => $page,
441 'orderby' => 'display_name',
442 'order' => 'ASC',
443 ];
444
445 /**
446 * Modify the arguments used for `WP_User_Query` constructor.
447 *
448 * @since 1.32.0
449 *
450 * @see https://codex.wordpress.org/Class_Reference/WP_User_Query
451 *
452 * @param array $search_args Argument array used in `WP_User_Query` constructor.
453 * @param string $term Search term.
454 * @param int[] $exclude Array of IDs to exclude.
455 * @param int $page Current page.
456 */
457 $search_args = apply_filters( 'job_manager_search_users_args', $search_args, $term, $exclude, $page );
458
459 $user_query = new WP_User_Query( $search_args );
460 $users = $user_query->get_results();
461 $total_pages = ceil( $user_query->get_total() / $per_page );
462 $more_exist = $total_pages > $page;
463 }
464
465 $found_users = [];
466
467 foreach ( $users as $user ) {
468 $found_users[ $user->ID ] = sprintf(
469 // translators: Used in user select. %1$s is the user's display name; #%2$s is the user ID; %3$s is the user email.
470 esc_html__( '%1$s (#%2$s – %3$s)', 'wp-job-manager' ),
471 htmlentities( $user->display_name ),
472 absint( $user->ID ),
473 $user->user_email
474 );
475 }
476
477 $response = [
478 'results' => $found_users,
479 'more' => $more_exist,
480 ];
481
482 /**
483 * Modify the search results response for users in ajax call.
484 *
485 * @since 1.32.0
486 *
487 * @param array $response {
488 * @type array $results Array of all found users; id => string descriptor
489 * @type boolean $more True if there is an additional page.
490 * }
491 * @param string $term Search term.
492 * @param int[] $exclude Array of IDs to exclude.
493 * @param int $page Current page.
494 */
495 $response = apply_filters( 'job_manager_search_users_response', $response, $term, $exclude, $page );
496
497 wp_send_json( $response );
498 }
499 }
500
501 WP_Job_Manager_Ajax::instance();
502