WP Job Manager - Version 1.34.4

Version Description

  • Fix: Harden security of job dashboard actions. Reported by Slavco.
  • Updated template: job-dashboard.php.
Download this release

Release Info

Developer jakeom
Plugin Icon 128x128 WP Job Manager
Version 1.34.4
Comparing to
See all releases

Code changes from version 1.34.3 to 1.34.4

changelog.txt CHANGED
@@ -1,3 +1,7 @@
 
 
 
 
1
  = 1.34.3 =
2
  * Fix: Hide filled listings in WordPress 5.5 sitemaps.
3
  * Fix: Issue with editing a job after getting to a preview step for another job.
1
+ = 1.34.4 =
2
+ * Fix: Harden security of job dashboard actions. Reported by Slavco.
3
+ * Updated template: `job-dashboard.php`.
4
+
5
  = 1.34.3 =
6
  * Fix: Hide filled listings in WordPress 5.5 sitemaps.
7
  * Fix: Issue with editing a job after getting to a preview step for another job.
includes/class-wp-job-manager-shortcodes.php CHANGED
@@ -24,6 +24,13 @@ class WP_Job_Manager_Shortcodes {
24
  */
25
  private $job_dashboard_message = '';
26
 
 
 
 
 
 
 
 
27
  /**
28
  * The single instance of the class.
29
  *
@@ -66,13 +73,13 @@ class WP_Job_Manager_Shortcodes {
66
  }
67
 
68
  /**
69
- * Helper function used to check if page is WPJM dashboard page.
70
- *
71
- * Checks if page has 'job_dashboard' shortcode.
72
- *
73
- * @access private
74
- * @return bool True if page is dashboard page, false otherwise.
75
- */
76
  private function is_job_dashboard_page() {
77
  global $post;
78
 
@@ -110,19 +117,27 @@ class WP_Job_Manager_Shortcodes {
110
  public function job_dashboard_handler() {
111
  if (
112
  ! empty( $_REQUEST['action'] )
 
113
  && ! empty( $_REQUEST['_wpnonce'] )
114
- && wp_verify_nonce( wp_unslash( $_REQUEST['_wpnonce'] ), 'job_manager_my_job_actions' ) // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Nonce should not be modified.
115
  ) {
116
 
117
- $action = sanitize_title( wp_unslash( $_REQUEST['action'] ) );
118
  $job_id = isset( $_REQUEST['job_id'] ) ? absint( $_REQUEST['job_id'] ) : 0;
 
119
 
120
- try {
121
- // Get Job.
122
- $job = get_post( $job_id );
 
 
 
 
 
 
 
 
123
 
124
- // Check ownership.
125
- if ( ! job_manager_user_can_edit_job( $job_id ) ) {
126
  throw new Exception( __( 'Invalid ID', 'wp-job-manager' ) );
127
  }
128
 
@@ -212,6 +227,60 @@ class WP_Job_Manager_Shortcodes {
212
  }
213
  }
214
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
215
  /**
216
  * Handles shortcode which lists the logged in user's jobs.
217
  *
@@ -250,21 +319,10 @@ class WP_Job_Manager_Shortcodes {
250
  }
251
 
252
  // ....If not show the job dashboard.
253
- $args = apply_filters(
254
- 'job_manager_get_dashboard_jobs_args',
255
- [
256
- 'post_type' => 'job_listing',
257
- 'post_status' => [ 'publish', 'expired', 'pending', 'draft', 'preview' ],
258
- 'ignore_sticky_posts' => 1,
259
- 'posts_per_page' => $posts_per_page,
260
- 'offset' => ( max( 1, get_query_var( 'paged' ) ) - 1 ) * $posts_per_page,
261
- 'orderby' => 'date',
262
- 'order' => 'desc',
263
- 'author' => get_current_user_id(),
264
- ]
265
- );
266
 
267
- $jobs = new WP_Query();
 
268
 
269
  echo wp_kses_post( $this->job_dashboard_message );
270
 
@@ -278,10 +336,16 @@ class WP_Job_Manager_Shortcodes {
278
  ]
279
  );
280
 
 
 
 
 
 
281
  get_job_manager_template(
282
  'job-dashboard.php',
283
  [
284
- 'jobs' => $jobs->query( $args ),
 
285
  'max_num_pages' => $jobs->max_num_pages,
286
  'job_dashboard_columns' => $job_dashboard_columns,
287
  ]
@@ -290,6 +354,102 @@ class WP_Job_Manager_Shortcodes {
290
  return ob_get_clean();
291
  }
292
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
293
  /**
294
  * Filters the url from paginate_links to avoid multiple calls for same action in job dashboard
295
  *
@@ -298,7 +458,8 @@ class WP_Job_Manager_Shortcodes {
298
  */
299
  public function filter_paginate_links( $link ) {
300
 
301
- if ( $this->is_job_dashboard_page() ) {
 
302
  return remove_query_arg( [ 'action', 'job_id', '_wpnonce' ], $link );
303
  }
304
 
@@ -385,8 +546,8 @@ class WP_Job_Manager_Shortcodes {
385
  $disable_client_state = true;
386
  }
387
  if ( ! empty( $_GET['search_location'] ) ) {
388
- $atts['location'] = sanitize_text_field( wp_unslash( $_GET['search_location'] ) );
389
- $disable_client_state = true;
390
  }
391
  if ( ! empty( $_GET['search_category'] ) ) {
392
  $atts['selected_category'] = sanitize_text_field( wp_unslash( $_GET['search_category'] ) );
24
  */
25
  private $job_dashboard_message = '';
26
 
27
+ /**
28
+ * Cache of job post IDs currently displayed on job dashboard.
29
+ *
30
+ * @var int[]
31
+ */
32
+ private $job_dashboard_job_ids;
33
+
34
  /**
35
  * The single instance of the class.
36
  *
73
  }
74
 
75
  /**
76
+ * Helper function used to check if page is WPJM dashboard page.
77
+ *
78
+ * Checks if page has 'job_dashboard' shortcode.
79
+ *
80
+ * @access private
81
+ * @return bool True if page is dashboard page, false otherwise.
82
+ */
83
  private function is_job_dashboard_page() {
84
  global $post;
85
 
117
  public function job_dashboard_handler() {
118
  if (
119
  ! empty( $_REQUEST['action'] )
120
+ && ! empty( $_REQUEST['job_id'] )
121
  && ! empty( $_REQUEST['_wpnonce'] )
 
122
  ) {
123
 
 
124
  $job_id = isset( $_REQUEST['job_id'] ) ? absint( $_REQUEST['job_id'] ) : 0;
125
+ $action = sanitize_title( wp_unslash( $_REQUEST['action'] ) );
126
 
127
+ $job = get_post( $job_id );
128
+ $job_actions = $this->get_job_actions( $job );
129
+
130
+ if (
131
+ ! isset( $job_actions[ $action ] )
132
+ || empty( $job_actions[ $action ]['nonce'] )
133
+ // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Nonce should not be modified.
134
+ || ! wp_verify_nonce( wp_unslash( $_REQUEST['_wpnonce'] ), $job_actions[ $action ]['nonce'] )
135
+ ) {
136
+ return;
137
+ }
138
 
139
+ try {
140
+ if ( empty( $job ) || 'job_listing' !== $job->post_type || ! job_manager_user_can_edit_job( $job_id ) ) {
141
  throw new Exception( __( 'Invalid ID', 'wp-job-manager' ) );
142
  }
143
 
227
  }
228
  }
229
 
230
+ /**
231
+ * Check if a job is listed on the current user's job dashboard page.
232
+ *
233
+ * @param WP_Post $job Job post object.
234
+ *
235
+ * @return bool
236
+ */
237
+ private function is_job_available_on_dashboard( WP_Post $job ) {
238
+ // Check cache of currently displayed job dashboard IDs first to avoid lots of queries.
239
+ if ( isset( $this->job_dashboard_job_ids ) && in_array( (int) $job->ID, $this->job_dashboard_job_ids, true ) ) {
240
+ return true;
241
+ }
242
+
243
+ $args = $this->get_job_dashboard_query_args();
244
+ $args['p'] = $job->ID;
245
+ $args['fields'] = 'ids';
246
+
247
+ $query = new WP_Query( $args );
248
+
249
+ return (int) $query->post_count > 0;
250
+ }
251
+
252
+ /**
253
+ * Helper that generates the job dashboard query args.
254
+ *
255
+ * @param int $posts_per_page Number of posts per page.
256
+ *
257
+ * @return array
258
+ */
259
+ private function get_job_dashboard_query_args( $posts_per_page = -1 ) {
260
+ $job_dashboard_args = [
261
+ 'post_type' => 'job_listing',
262
+ 'post_status' => [ 'publish', 'expired', 'pending', 'draft', 'preview' ],
263
+ 'ignore_sticky_posts' => 1,
264
+ 'posts_per_page' => $posts_per_page,
265
+ 'orderby' => 'date',
266
+ 'order' => 'desc',
267
+ 'author' => get_current_user_id(),
268
+ ];
269
+
270
+ if ( $posts_per_page > 0 ) {
271
+ $job_dashboard_args['offset'] = ( max( 1, get_query_var( 'paged' ) ) - 1 ) * $posts_per_page;
272
+ }
273
+
274
+ /**
275
+ * Customize the query that is used to get jobs on the job dashboard.
276
+ *
277
+ * @since 1.0.0
278
+ *
279
+ * @param array $job_dashboard_args Arguments to pass to WP_Query.
280
+ */
281
+ return apply_filters( 'job_manager_get_dashboard_jobs_args', $job_dashboard_args );
282
+ }
283
+
284
  /**
285
  * Handles shortcode which lists the logged in user's jobs.
286
  *
319
  }
320
 
321
  // ....If not show the job dashboard.
322
+ $jobs = new WP_Query( $this->get_job_dashboard_query_args( $posts_per_page ) );
 
 
 
 
 
 
 
 
 
 
 
 
323
 
324
+ // Cache IDs for access check later on.
325
+ $this->job_dashboard_job_ids = wp_list_pluck( $jobs->posts, 'ID' );
326
 
327
  echo wp_kses_post( $this->job_dashboard_message );
328
 
336
  ]
337
  );
338
 
339
+ $job_actions = [];
340
+ foreach ( $jobs->posts as $job ) {
341
+ $job_actions[ $job->ID ] = $this->get_job_actions( $job );
342
+ }
343
+
344
  get_job_manager_template(
345
  'job-dashboard.php',
346
  [
347
+ 'jobs' => $jobs->posts,
348
+ 'job_actions' => $job_actions,
349
  'max_num_pages' => $jobs->max_num_pages,
350
  'job_dashboard_columns' => $job_dashboard_columns,
351
  ]
354
  return ob_get_clean();
355
  }
356
 
357
+ /**
358
+ * Get the actions available to the user for a job listing on the job dashboard page.
359
+ *
360
+ * @param WP_Post $job The job post object.
361
+ *
362
+ * @return array
363
+ */
364
+ public function get_job_actions( $job ) {
365
+ if (
366
+ ! get_current_user_id()
367
+ || ! $job instanceof WP_Post
368
+ || 'job_listing' !== $job->post_type
369
+ || ! $this->is_job_available_on_dashboard( $job )
370
+ ) {
371
+ return [];
372
+ }
373
+
374
+ $base_nonce_action_name = 'job_manager_my_job_actions';
375
+
376
+ $actions = [];
377
+ switch ( $job->post_status ) {
378
+ case 'publish':
379
+ if ( WP_Job_Manager_Post_Types::job_is_editable( $job->ID ) ) {
380
+ $actions['edit'] = [
381
+ 'label' => __( 'Edit', 'wp-job-manager' ),
382
+ 'nonce' => false,
383
+ ];
384
+ }
385
+ if ( is_position_filled( $job ) ) {
386
+ $actions['mark_not_filled'] = [
387
+ 'label' => __( 'Mark not filled', 'wp-job-manager' ),
388
+ 'nonce' => $base_nonce_action_name,
389
+ ];
390
+ } else {
391
+ $actions['mark_filled'] = [
392
+ 'label' => __( 'Mark filled', 'wp-job-manager' ),
393
+ 'nonce' => $base_nonce_action_name,
394
+ ];
395
+ }
396
+
397
+ $actions['duplicate'] = [
398
+ 'label' => __( 'Duplicate', 'wp-job-manager' ),
399
+ 'nonce' => $base_nonce_action_name,
400
+ ];
401
+ break;
402
+ case 'expired':
403
+ if ( job_manager_get_permalink( 'submit_job_form' ) ) {
404
+ $actions['relist'] = [
405
+ 'label' => __( 'Relist', 'wp-job-manager' ),
406
+ 'nonce' => $base_nonce_action_name,
407
+ ];
408
+ }
409
+ break;
410
+ case 'pending_payment':
411
+ case 'pending':
412
+ if ( WP_Job_Manager_Post_Types::job_is_editable( $job->ID ) ) {
413
+ $actions['edit'] = [
414
+ 'label' => __( 'Edit', 'wp-job-manager' ),
415
+ 'nonce' => false,
416
+ ];
417
+ }
418
+ break;
419
+ case 'draft':
420
+ case 'preview':
421
+ $actions['continue'] = [
422
+ 'label' => __( 'Continue Submission', 'wp-job-manager' ),
423
+ 'nonce' => $base_nonce_action_name,
424
+ ];
425
+ break;
426
+ }
427
+
428
+ $actions['delete'] = [
429
+ 'label' => __( 'Delete', 'wp-job-manager' ),
430
+ 'nonce' => $base_nonce_action_name,
431
+ ];
432
+
433
+ /**
434
+ * Filter the actions available to the current user for a job on the job dashboard page.
435
+ *
436
+ * @since 1.0.0
437
+ *
438
+ * @param array $actions Actions to filter.
439
+ * @param WP_Post $job Job post object.
440
+ */
441
+ $actions = apply_filters( 'job_manager_my_job_actions', $actions, $job );
442
+
443
+ // For backwards compatibility, convert `nonce => true` to the nonce action name.
444
+ foreach ( $actions as $key => $action ) {
445
+ if ( true === $action['nonce'] ) {
446
+ $actions[ $key ]['nonce'] = $base_nonce_action_name;
447
+ }
448
+ }
449
+
450
+ return $actions;
451
+ }
452
+
453
  /**
454
  * Filters the url from paginate_links to avoid multiple calls for same action in job dashboard
455
  *
458
  */
459
  public function filter_paginate_links( $link ) {
460
 
461
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Input is used for comparison only.
462
+ if ( $this->is_job_dashboard_page() && isset( $_GET['action'] ) && in_array( $_GET['action'], [ 'mark_filled', 'mark_not_filled' ], true ) ) {
463
  return remove_query_arg( [ 'action', 'job_id', '_wpnonce' ], $link );
464
  }
465
 
546
  $disable_client_state = true;
547
  }
548
  if ( ! empty( $_GET['search_location'] ) ) {
549
+ $atts['location'] = sanitize_text_field( wp_unslash( $_GET['search_location'] ) );
550
+ $disable_client_state = true;
551
  }
552
  if ( ! empty( $_GET['search_category'] ) ) {
553
  $atts['selected_category'] = sanitize_text_field( wp_unslash( $_GET['search_category'] ) );
languages/wp-job-manager.pot CHANGED
@@ -2,9 +2,9 @@
2
  # This file is distributed under the GPL2+.
3
  msgid ""
4
  msgstr ""
5
- "Project-Id-Version: WP Job Manager 1.34.3\n"
6
  "Report-Msgid-Bugs-To: https://github.com/Automattic/WP-Job-Manager/issues\n"
7
- "POT-Creation-Date: 2020-08-07 14:53:28+00:00\n"
8
  "MIME-Version: 1.0\n"
9
  "Content-Type: text/plain; charset=utf-8\n"
10
  "Content-Transfer-Encoding: 8bit\n"
@@ -230,7 +230,7 @@ msgid "Featured?"
230
  msgstr ""
231
 
232
  #: includes/admin/class-wp-job-manager-cpt.php:503
233
- #: includes/class-wp-job-manager-shortcodes.php:275
234
  msgid "Filled?"
235
  msgstr ""
236
 
@@ -268,12 +268,13 @@ msgstr ""
268
 
269
  #: includes/admin/class-wp-job-manager-cpt.php:647
270
  #: includes/class-wp-job-manager-post-types.php:335
271
- #: templates/job-dashboard.php:52 templates/job-dashboard.php:70
 
272
  msgid "Edit"
273
  msgstr ""
274
 
275
  #: includes/admin/class-wp-job-manager-cpt.php:654
276
- #: templates/job-dashboard.php:79
277
  msgid "Delete"
278
  msgstr ""
279
 
@@ -1385,55 +1386,75 @@ msgstr ""
1385
  msgid "Listing Expiry Date"
1386
  msgstr ""
1387
 
1388
- #: includes/class-wp-job-manager-shortcodes.php:126
1389
  msgid "Invalid ID"
1390
  msgstr ""
1391
 
1392
- #: includes/class-wp-job-manager-shortcodes.php:133
1393
  msgid "This position has already been filled"
1394
  msgstr ""
1395
 
1396
- #: includes/class-wp-job-manager-shortcodes.php:141
1397
  #. translators: Placeholder %s is the job listing title.
1398
  msgid "%s has been filled"
1399
  msgstr ""
1400
 
1401
- #: includes/class-wp-job-manager-shortcodes.php:146
1402
  msgid "This position is not filled"
1403
  msgstr ""
1404
 
1405
- #: includes/class-wp-job-manager-shortcodes.php:154
1406
  #. translators: Placeholder %s is the job listing title.
1407
  msgid "%s has been marked as not filled"
1408
  msgstr ""
1409
 
1410
- #: includes/class-wp-job-manager-shortcodes.php:162
1411
  #. translators: Placeholder %s is the job listing title.
1412
  msgid "%s has been deleted"
1413
  msgstr ""
1414
 
1415
- #: includes/class-wp-job-manager-shortcodes.php:167
1416
- #: includes/class-wp-job-manager-shortcodes.php:181
1417
  msgid "Missing submission page."
1418
  msgstr ""
1419
 
1420
- #: includes/class-wp-job-manager-shortcodes.php:274
1421
  #: includes/widgets/class-wp-job-manager-widget-featured-jobs.php:36
1422
  #: includes/widgets/class-wp-job-manager-widget-featured-jobs.php:52
1423
  #: includes/widgets/class-wp-job-manager-widget-recent-jobs.php:36
1424
  msgid "Title"
1425
  msgstr ""
1426
 
1427
- #: includes/class-wp-job-manager-shortcodes.php:276
1428
  msgid "Date Posted"
1429
  msgstr ""
1430
 
1431
- #: includes/class-wp-job-manager-shortcodes.php:277
1432
  msgid "Listing Expires"
1433
  msgstr ""
1434
 
1435
- #: includes/class-wp-job-manager-shortcodes.php:456
1436
- #: includes/class-wp-job-manager-shortcodes.php:494
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1437
  msgid "Load more listings"
1438
  msgstr ""
1439
 
@@ -2118,38 +2139,18 @@ msgstr ""
2118
  msgid "You need to be signed in to manage your listings."
2119
  msgstr ""
2120
 
2121
- #: templates/job-dashboard.php:19
2122
  msgid "Your listings are shown in the table below."
2123
  msgstr ""
2124
 
2125
- #: templates/job-dashboard.php:31
2126
  msgid "You do not have any active listings."
2127
  msgstr ""
2128
 
2129
- #: templates/job-dashboard.php:44
2130
  msgid "Featured Job"
2131
  msgstr ""
2132
 
2133
- #: templates/job-dashboard.php:55
2134
- msgid "Mark not filled"
2135
- msgstr ""
2136
-
2137
- #: templates/job-dashboard.php:57
2138
- msgid "Mark filled"
2139
- msgstr ""
2140
-
2141
- #: templates/job-dashboard.php:60
2142
- msgid "Duplicate"
2143
- msgstr ""
2144
-
2145
- #: templates/job-dashboard.php:64
2146
- msgid "Relist"
2147
- msgstr ""
2148
-
2149
- #: templates/job-dashboard.php:75
2150
- msgid "Continue Submission"
2151
- msgstr ""
2152
-
2153
  #: templates/job-filters.php:30 templates/job-filters.php:31
2154
  msgid "Keywords"
2155
  msgstr ""
2
  # This file is distributed under the GPL2+.
3
  msgid ""
4
  msgstr ""
5
+ "Project-Id-Version: WP Job Manager 1.34.4\n"
6
  "Report-Msgid-Bugs-To: https://github.com/Automattic/WP-Job-Manager/issues\n"
7
+ "POT-Creation-Date: 2020-11-23 22:28:27+00:00\n"
8
  "MIME-Version: 1.0\n"
9
  "Content-Type: text/plain; charset=utf-8\n"
10
  "Content-Transfer-Encoding: 8bit\n"
230
  msgstr ""
231
 
232
  #: includes/admin/class-wp-job-manager-cpt.php:503
233
+ #: includes/class-wp-job-manager-shortcodes.php:333
234
  msgid "Filled?"
235
  msgstr ""
236
 
268
 
269
  #: includes/admin/class-wp-job-manager-cpt.php:647
270
  #: includes/class-wp-job-manager-post-types.php:335
271
+ #: includes/class-wp-job-manager-shortcodes.php:381
272
+ #: includes/class-wp-job-manager-shortcodes.php:414
273
  msgid "Edit"
274
  msgstr ""
275
 
276
  #: includes/admin/class-wp-job-manager-cpt.php:654
277
+ #: includes/class-wp-job-manager-shortcodes.php:429
278
  msgid "Delete"
279
  msgstr ""
280
 
1386
  msgid "Listing Expiry Date"
1387
  msgstr ""
1388
 
1389
+ #: includes/class-wp-job-manager-shortcodes.php:141
1390
  msgid "Invalid ID"
1391
  msgstr ""
1392
 
1393
+ #: includes/class-wp-job-manager-shortcodes.php:148
1394
  msgid "This position has already been filled"
1395
  msgstr ""
1396
 
1397
+ #: includes/class-wp-job-manager-shortcodes.php:156
1398
  #. translators: Placeholder %s is the job listing title.
1399
  msgid "%s has been filled"
1400
  msgstr ""
1401
 
1402
+ #: includes/class-wp-job-manager-shortcodes.php:161
1403
  msgid "This position is not filled"
1404
  msgstr ""
1405
 
1406
+ #: includes/class-wp-job-manager-shortcodes.php:169
1407
  #. translators: Placeholder %s is the job listing title.
1408
  msgid "%s has been marked as not filled"
1409
  msgstr ""
1410
 
1411
+ #: includes/class-wp-job-manager-shortcodes.php:177
1412
  #. translators: Placeholder %s is the job listing title.
1413
  msgid "%s has been deleted"
1414
  msgstr ""
1415
 
1416
+ #: includes/class-wp-job-manager-shortcodes.php:182
1417
+ #: includes/class-wp-job-manager-shortcodes.php:196
1418
  msgid "Missing submission page."
1419
  msgstr ""
1420
 
1421
+ #: includes/class-wp-job-manager-shortcodes.php:332
1422
  #: includes/widgets/class-wp-job-manager-widget-featured-jobs.php:36
1423
  #: includes/widgets/class-wp-job-manager-widget-featured-jobs.php:52
1424
  #: includes/widgets/class-wp-job-manager-widget-recent-jobs.php:36
1425
  msgid "Title"
1426
  msgstr ""
1427
 
1428
+ #: includes/class-wp-job-manager-shortcodes.php:334
1429
  msgid "Date Posted"
1430
  msgstr ""
1431
 
1432
+ #: includes/class-wp-job-manager-shortcodes.php:335
1433
  msgid "Listing Expires"
1434
  msgstr ""
1435
 
1436
+ #: includes/class-wp-job-manager-shortcodes.php:387
1437
+ msgid "Mark not filled"
1438
+ msgstr ""
1439
+
1440
+ #: includes/class-wp-job-manager-shortcodes.php:392
1441
+ msgid "Mark filled"
1442
+ msgstr ""
1443
+
1444
+ #: includes/class-wp-job-manager-shortcodes.php:398
1445
+ msgid "Duplicate"
1446
+ msgstr ""
1447
+
1448
+ #: includes/class-wp-job-manager-shortcodes.php:405
1449
+ msgid "Relist"
1450
+ msgstr ""
1451
+
1452
+ #: includes/class-wp-job-manager-shortcodes.php:422
1453
+ msgid "Continue Submission"
1454
+ msgstr ""
1455
+
1456
+ #: includes/class-wp-job-manager-shortcodes.php:617
1457
+ #: includes/class-wp-job-manager-shortcodes.php:655
1458
  msgid "Load more listings"
1459
  msgstr ""
1460
 
2139
  msgid "You need to be signed in to manage your listings."
2140
  msgstr ""
2141
 
2142
+ #: templates/job-dashboard.php:26
2143
  msgid "Your listings are shown in the table below."
2144
  msgstr ""
2145
 
2146
+ #: templates/job-dashboard.php:38
2147
  msgid "You do not have any active listings."
2148
  msgstr ""
2149
 
2150
+ #: templates/job-dashboard.php:51
2151
  msgid "Featured Job"
2152
  msgstr ""
2153
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2154
  #: templates/job-filters.php:30 templates/job-filters.php:31
2155
  msgid "Keywords"
2156
  msgstr ""
readme.txt CHANGED
@@ -2,9 +2,9 @@
2
  Contributors: mikejolley, automattic, adamkheckler, alexsanford1, annezazu, cena, chaselivingston, csonnek, davor.altman, donnapep, donncha, drawmyface, erania-pinnera, jacobshere, jakeom, jeherve, jenhooks, jgs, jonryan, kraftbj, lamdayap, lschuyler, macmanx, nancythanki, orangesareorange, rachelsquirrel, ryancowles, richardmtl, scarstocea
3
  Tags: job manager, job listing, job board, job management, job lists, job list, job, jobs, company, hiring, employment, employer, employees, candidate, freelance, internship, job listings, positions, board, application, hiring, listing, manager, recruiting, recruitment, talent
4
  Requires at least: 5.2
5
- Tested up to: 5.5
6
  Requires PHP: 7.0
7
- Stable tag: 1.34.3
8
  License: GPLv3
9
  License URI: http://www.gnu.org/licenses/gpl-3.0.html
10
 
@@ -153,6 +153,10 @@ It then creates a database based on the parameters passed to it.
153
 
154
  == Changelog ==
155
 
 
 
 
 
156
  = 1.34.3 =
157
  * Fix: Hide filled listings in WordPress 5.5 sitemaps.
158
  * Fix: Issue with editing a job after getting to a preview step for another job.
2
  Contributors: mikejolley, automattic, adamkheckler, alexsanford1, annezazu, cena, chaselivingston, csonnek, davor.altman, donnapep, donncha, drawmyface, erania-pinnera, jacobshere, jakeom, jeherve, jenhooks, jgs, jonryan, kraftbj, lamdayap, lschuyler, macmanx, nancythanki, orangesareorange, rachelsquirrel, ryancowles, richardmtl, scarstocea
3
  Tags: job manager, job listing, job board, job management, job lists, job list, job, jobs, company, hiring, employment, employer, employees, candidate, freelance, internship, job listings, positions, board, application, hiring, listing, manager, recruiting, recruitment, talent
4
  Requires at least: 5.2
5
+ Tested up to: 5.6
6
  Requires PHP: 7.0
7
+ Stable tag: 1.34.4
8
  License: GPLv3
9
  License URI: http://www.gnu.org/licenses/gpl-3.0.html
10
 
153
 
154
  == Changelog ==
155
 
156
+ = 1.34.4 =
157
+ * Fix: Harden security of job dashboard actions. Reported by Slavco.
158
+ * Updated template: `job-dashboard.php`.
159
+
160
  = 1.34.3 =
161
  * Fix: Hide filled listings in WordPress 5.5 sitemaps.
162
  * Fix: Issue with editing a job after getting to a preview step for another job.
templates/job-dashboard.php CHANGED
@@ -8,7 +8,14 @@
8
  * @author Automattic
9
  * @package wp-job-manager
10
  * @category Template
11
- * @version 1.34.1
 
 
 
 
 
 
 
12
  */
13
 
14
  if ( ! defined( 'ABSPATH' ) ) {
@@ -44,47 +51,17 @@ if ( ! defined( 'ABSPATH' ) ) {
44
  <?php echo is_position_featured( $job ) ? '<span class="featured-job-icon" title="' . esc_attr__( 'Featured Job', 'wp-job-manager' ) . '"></span>' : ''; ?>
45
  <ul class="job-dashboard-actions">
46
  <?php
47
- $actions = [];
48
-
49
- switch ( $job->post_status ) {
50
- case 'publish' :
51
- if ( WP_Job_Manager_Post_Types::job_is_editable( $job->ID ) ) {
52
- $actions[ 'edit' ] = [ 'label' => __( 'Edit', 'wp-job-manager' ), 'nonce' => false ];
53
- }
54
- if ( is_position_filled( $job ) ) {
55
- $actions['mark_not_filled'] = [ 'label' => __( 'Mark not filled', 'wp-job-manager' ), 'nonce' => true ];
56
- } else {
57
- $actions['mark_filled'] = [ 'label' => __( 'Mark filled', 'wp-job-manager' ), 'nonce' => true ];
58
- }
59
-
60
- $actions['duplicate'] = [ 'label' => __( 'Duplicate', 'wp-job-manager' ), 'nonce' => true ];
61
- break;
62
- case 'expired' :
63
- if ( job_manager_get_permalink( 'submit_job_form' ) ) {
64
- $actions['relist'] = [ 'label' => __( 'Relist', 'wp-job-manager' ), 'nonce' => true ];
65
- }
66
- break;
67
- case 'pending_payment' :
68
- case 'pending' :
69
- if ( WP_Job_Manager_Post_Types::job_is_editable( $job->ID ) ) {
70
- $actions['edit'] = [ 'label' => __( 'Edit', 'wp-job-manager' ), 'nonce' => false ];
71
  }
72
- break;
73
- case 'draft' :
74
- case 'preview' :
75
- $actions['continue'] = [ 'label' => __( 'Continue Submission', 'wp-job-manager' ), 'nonce' => true ];
76
- break;
77
- }
78
-
79
- $actions['delete'] = [ 'label' => __( 'Delete', 'wp-job-manager' ), 'nonce' => true ];
80
- $actions = apply_filters( 'job_manager_my_job_actions', $actions, $job );
81
-
82
- foreach ( $actions as $action => $value ) {
83
- $action_url = add_query_arg( [ 'action' => $action, 'job_id' => $job->ID ] );
84
- if ( $value['nonce'] ) {
85
- $action_url = wp_nonce_url( $action_url, 'job_manager_my_job_actions' );
86
  }
87
- echo '<li><a href="' . esc_url( $action_url ) . '" class="job-dashboard-action-' . esc_attr( $action ) . '">' . esc_html( $value['label'] ) . '</a></li>';
88
  }
89
  ?>
90
  </ul>
8
  * @author Automattic
9
  * @package wp-job-manager
10
  * @category Template
11
+ * @version 1.34.4
12
+ *
13
+ * @since 1.34.4 Available job actions are passed in an array (`$job_actions`, keyed by job ID) and not generated in the template.
14
+ *
15
+ * @var array $job_dashboard_columns Array of the columns to show on the job dashboard page.
16
+ * @var int $max_num_pages Maximum number of pages
17
+ * @var WP_Post[] $jobs Array of job post results.
18
+ * @var array $job_actions Array of actions available for each job.
19
  */
20
 
21
  if ( ! defined( 'ABSPATH' ) ) {
51
  <?php echo is_position_featured( $job ) ? '<span class="featured-job-icon" title="' . esc_attr__( 'Featured Job', 'wp-job-manager' ) . '"></span>' : ''; ?>
52
  <ul class="job-dashboard-actions">
53
  <?php
54
+ if ( ! empty( $job_actions[ $job->ID ] ) ) {
55
+ foreach ( $job_actions[ $job->ID ] as $action => $value ) {
56
+ $action_url = add_query_arg( [
57
+ 'action' => $action,
58
+ 'job_id' => $job->ID
59
+ ] );
60
+ if ( $value['nonce'] ) {
61
+ $action_url = wp_nonce_url( $action_url, $value['nonce'] );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
  }
63
+ echo '<li><a href="' . esc_url( $action_url ) . '" class="job-dashboard-action-' . esc_attr( $action ) . '">' . esc_html( $value['label'] ) . '</a></li>';
 
 
 
 
 
 
 
 
 
 
 
 
 
64
  }
 
65
  }
66
  ?>
67
  </ul>
wp-job-manager-functions.php CHANGED
@@ -718,7 +718,7 @@ function job_manager_user_can_edit_job( $job_id ) {
718
  } else {
719
  $job = get_post( $job_id );
720
 
721
- if ( ! $job || ( absint( $job->post_author ) !== get_current_user_id() && ! current_user_can( 'edit_post', $job_id ) ) ) {
722
  $can_edit = false;
723
  }
724
  }
@@ -1464,7 +1464,7 @@ function job_manager_duplicate_listing( $post_id ) {
1464
  }
1465
 
1466
  $post = get_post( $post_id );
1467
- if ( ! $post ) {
1468
  return 0;
1469
  }
1470
 
@@ -1509,14 +1509,21 @@ function job_manager_duplicate_listing( $post_id ) {
1509
  if ( ! empty( $post_meta ) ) {
1510
  $post_meta = wp_list_pluck( $post_meta, 'meta_value', 'meta_key' );
1511
 
1512
- $default_duplicate_ignore_keys = [ '_filled', '_featured', '_job_expires', '_job_duration', '_package_id', '_user_package_id' ];
1513
  $duplicate_ignore_keys = apply_filters( 'job_manager_duplicate_listing_ignore_keys', $default_duplicate_ignore_keys, true );
1514
 
1515
  foreach ( $post_meta as $meta_key => $meta_value ) {
1516
- if ( in_array( $meta_key, $duplicate_ignore_keys, true ) ) {
 
 
1517
  continue;
1518
  }
1519
- update_post_meta( $new_post_id, $meta_key, maybe_unserialize( $meta_value ) );
 
 
 
 
 
1520
  }
1521
  }
1522
 
718
  } else {
719
  $job = get_post( $job_id );
720
 
721
+ if ( ! $job || 'job_listing' !== $job->post_type || ( absint( $job->post_author ) !== get_current_user_id() && ! current_user_can( 'edit_post', $job_id ) ) ) {
722
  $can_edit = false;
723
  }
724
  }
1464
  }
1465
 
1466
  $post = get_post( $post_id );
1467
+ if ( ! $post || 'job_listing' !== $post->post_type ) {
1468
  return 0;
1469
  }
1470
 
1509
  if ( ! empty( $post_meta ) ) {
1510
  $post_meta = wp_list_pluck( $post_meta, 'meta_value', 'meta_key' );
1511
 
1512
+ $default_duplicate_ignore_keys = [ '_filled', '_featured', '_job_expires', '_job_duration', '_package_id', '_user_package_id', '_edit_lock', '_submitting_key', '_tracked_submitted', '_tracked_approved' ];
1513
  $duplicate_ignore_keys = apply_filters( 'job_manager_duplicate_listing_ignore_keys', $default_duplicate_ignore_keys, true );
1514
 
1515
  foreach ( $post_meta as $meta_key => $meta_value ) {
1516
+ $sanitized_key = preg_replace( "/[^\x20-\x7E]/", '', $meta_key );
1517
+
1518
+ if ( in_array( $sanitized_key, $duplicate_ignore_keys, true ) ) {
1519
  continue;
1520
  }
1521
+
1522
+ if ( 1 === preg_match( '/^(_wp_|_oembed_)/', $sanitized_key ) ) {
1523
+ continue;
1524
+ }
1525
+
1526
+ update_post_meta( $new_post_id, wp_slash( $meta_key ), wp_slash( maybe_unserialize( $meta_value ) ) );
1527
  }
1528
  }
1529
 
wp-job-manager.php CHANGED
@@ -3,11 +3,11 @@
3
  * Plugin Name: WP Job Manager
4
  * Plugin URI: https://wpjobmanager.com/
5
  * Description: Manage job listings from the WordPress admin panel, and allow users to post jobs directly to your site.
6
- * Version: 1.34.3
7
  * Author: Automattic
8
  * Author URI: https://wpjobmanager.com/
9
  * Requires at least: 5.2
10
- * Tested up to: 5.5
11
  * Requires PHP: 7.0
12
  * Text Domain: wp-job-manager
13
  * Domain Path: /languages/
@@ -21,7 +21,7 @@ if ( ! defined( 'ABSPATH' ) ) {
21
  }
22
 
23
  // Define constants.
24
- define( 'JOB_MANAGER_VERSION', '1.34.3' );
25
  define( 'JOB_MANAGER_PLUGIN_DIR', untrailingslashit( plugin_dir_path( __FILE__ ) ) );
26
  define( 'JOB_MANAGER_PLUGIN_URL', untrailingslashit( plugins_url( basename( plugin_dir_path( __FILE__ ) ), basename( __FILE__ ) ) ) );
27
  define( 'JOB_MANAGER_PLUGIN_BASENAME', plugin_basename( __FILE__ ) );
3
  * Plugin Name: WP Job Manager
4
  * Plugin URI: https://wpjobmanager.com/
5
  * Description: Manage job listings from the WordPress admin panel, and allow users to post jobs directly to your site.
6
+ * Version: 1.34.4
7
  * Author: Automattic
8
  * Author URI: https://wpjobmanager.com/
9
  * Requires at least: 5.2
10
+ * Tested up to: 5.6
11
  * Requires PHP: 7.0
12
  * Text Domain: wp-job-manager
13
  * Domain Path: /languages/
21
  }
22
 
23
  // Define constants.
24
+ define( 'JOB_MANAGER_VERSION', '1.34.4' );
25
  define( 'JOB_MANAGER_PLUGIN_DIR', untrailingslashit( plugin_dir_path( __FILE__ ) ) );
26
  define( 'JOB_MANAGER_PLUGIN_URL', untrailingslashit( plugins_url( basename( plugin_dir_path( __FILE__ ) ), basename( __FILE__ ) ) ) );
27
  define( 'JOB_MANAGER_PLUGIN_BASENAME', plugin_basename( __FILE__ ) );