<?php
namespace jb\ajax;
use WP_Query;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
if ( ! class_exists( 'jb\ajax\Jobs' ) ) {
/**
* Class Jobs
*
* @package jb\ajax
*/
class Jobs {
/**
* @var int
*
* @since 1.0
*/
public $jobs_per_page;
/**
* @var array
*
* @since 1.0
*/
public $query_args = array();
/**
* @var string
*
* @since 1.0
*/
public $search = '';
/**
* @var string
*
* @since 1.0
*/
public $company_name_meta = '';
/**
* Jobs constructor.
*/
public function __construct() {
add_action( 'wp_loaded', array( $this, 'init_variables' ) );
}
/**
* Init variables
*
* @since 1.0
*/
public function init_variables() {
// phpcs:ignore WordPress.Security.NonceVerification -- already verified here
$this->jobs_per_page = ! empty( $_POST['per_page'] ) ? absint( $_POST['per_page'] ) : JB()->options()->get( 'jobs-list-pagination' );
}
/**
* Replace 'WHERE' by the searching request
*
* @param string $where
*
* @return string
*
* @since 1.0
*/
public function change_where_posts( $where ) {
// phpcs:ignore WordPress.Security.NonceVerification -- already verified here
if ( ! empty( $_POST['search'] ) ) {
$from = '/' . preg_quote( $this->search, '/' ) . '/';
$where = preg_replace( $from, '', $where, 1 );
}
return $where;
}
/**
* Set class search variable
*
* @param string $search
*
* @return string
*
* @since 1.0
*/
public function set_search( $search ) {
$this->search = $search;
return $search;
}
/**
* Change mySQL meta query join attribute
* for search by the company name
*
* @param array $sql Array containing the query's JOIN and WHERE clauses.
*
* @return array
*
* @since 1.0
*/
public function change_meta_sql( $sql ) {
// phpcs:disable WordPress.Security.NonceVerification
if ( ! empty( $_POST['search'] ) ) {
global $wpdb;
$search = sanitize_text_field( wp_unslash( $_POST['search'] ) ); // phpcs:ignore WordPress.Security.NonceVerification -- already verified here
if ( ! empty( $search ) ) {
$meta_value = '%' . $wpdb->esc_like( $search ) . '%';
$search_meta = $wpdb->prepare( '%s', $meta_value );
preg_match(
"/\(\s(.*).meta_key = \'jb-company-name\'[^\)]/im",
$sql['where'],
$join_matches
);
$from = '/' . preg_quote( ' AND ', '/' ) . '/';
$search_query = preg_replace( $from, ' OR ', $this->search, 1 );
if ( isset( $join_matches[1] ) ) {
$meta_join_for_search = trim( $join_matches[1] );
$this->company_name_meta = $meta_join_for_search;
preg_match( '~(?<=\{)(.*?)(?=\})~', $search_meta, $matches, PREG_OFFSET_CAPTURE, 0 );
// workaround for standard mySQL hashes which are used by $wpdb->prepare instead of the %symbol
// sometimes it breaks error for strings like that wp_postmeta.meta_value LIKE '{12f209b48a89eeab33424902879d05d503f251ca8812dde03b59484a2991dc74}AMS{12f209b48a89eeab33424902879d05d503f251ca8812dde03b59484a2991dc74}'
// {12f209b48a89eeab33424902879d05d503f251ca8812dde03b59484a2991dc74} isn't applied by the `preg_replace()` below
if ( $matches[0][0] ) {
$search_meta = str_replace(
array(
'{' . $matches[0][0] . '}',
'/', // it's required for line 161 - preg_replace
),
array(
'#%&',
'\/', // it's required for line 161 - preg_replace
),
$search_meta
);
$search_query = str_replace(
array(
'{' . $matches[0][0] . '}',
'/', // it's required for line 161 - preg_replace
),
array(
'#%&',
'\/', // it's required for line 161 - preg_replace
),
$search_query
);
$sql['where'] = str_replace( '{' . $matches[0][0] . '}', '#%&', $sql['where'] );
}
// phpcs:disable Squiz.Strings.DoubleQuoteUsage.NotRequired -- don't remove regex indentation
$sql['where'] = preg_replace(
"/\( (" . $meta_join_for_search . ".meta_key = 'jb-company-name' AND " . $meta_join_for_search . ".meta_value LIKE " . $search_meta . ") \)/im",
"( $1 " . $search_query . " )",
$sql['where'],
1
);
if ( $matches[0][0] && ! empty( $sql['where'] ) ) {
$sql['where'] = str_replace( '#%&', '{' . $matches[0][0] . '}', $sql['where'] );
}
// phpcs:enable Squiz.Strings.DoubleQuoteUsage.NotRequired -- don't remove regex indentation
}
}
}
if ( JB()->options()->get( 'job-salary' ) ) {
if ( ! empty( $_POST['salary'] ) ) {
global $wpdb;
$salary = explode( '-', wp_unslash( $_POST['salary'] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- sanitized below via absint per value
$min = absint( $salary[0] );
$max = absint( $salary[1] );
$sql['join'] .= "
LEFT JOIN $wpdb->postmeta AS jb_salary_type ON ( $wpdb->posts.ID = jb_salary_type.post_id AND jb_salary_type.meta_key = 'jb-salary-type' )
LEFT JOIN $wpdb->postmeta AS jb_amount_type ON ( $wpdb->posts.ID = jb_amount_type.post_id AND jb_amount_type.meta_key = 'jb-salary-amount-type' )
LEFT JOIN $wpdb->postmeta AS jb_amount ON ( $wpdb->posts.ID = jb_amount.post_id AND jb_amount.meta_key = 'jb-salary-amount' )
LEFT JOIN $wpdb->postmeta AS jb_min_amount ON ( $wpdb->posts.ID = jb_min_amount.post_id AND jb_min_amount.meta_key = 'jb-salary-min-amount' )
LEFT JOIN $wpdb->postmeta AS jb_max_amount ON ( $wpdb->posts.ID = jb_max_amount.post_id AND jb_max_amount.meta_key = 'jb-salary-max-amount' )
";
$sql['where'] .= " AND (
(jb_salary_type.meta_key IS NOT NULL) AND
(jb_salary_type.meta_value != '') AND
(jb_salary_type.meta_value IN ('fixed', 'recurring') AND jb_amount_type.meta_value = 'numeric' AND jb_amount.meta_value BETWEEN $min AND $max) OR
(jb_salary_type.meta_value IN ('fixed', 'recurring') AND jb_amount_type.meta_value = 'range' AND ( jb_min_amount.meta_value BETWEEN $min AND $max OR jb_max_amount.meta_value BETWEEN $min AND $max) )
)";
}
}
// phpcs:enable WordPress.Security.NonceVerification
return $sql;
}
/**
* Searching by relevance
*
* @param string $search_orderby
* @param WP_Query $query
*
* @return string
*
* @since 1.0
*/
public function relevance_search( /** @noinspection PhpUnusedParameterInspection */$search_orderby, $query ) {
global $wpdb;
$orderby_array = array();
// phpcs:disable WordPress.Security.NonceVerification -- already verified here
$search = ! empty( $_POST['search'] ) ? sanitize_text_field( wp_unslash( $_POST['search'] ) ) : '';
$meta_value = '%' . $wpdb->esc_like( $search ) . '%';
// Sentence match in 'post_title'.
$new_search_orderby = '';
if ( $meta_value ) {
$new_search_orderby .= $wpdb->prepare( "WHEN {$wpdb->posts}.post_title LIKE %s THEN 1 ", $meta_value );
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $this->company_name_meta is static variable
$new_search_orderby .= $wpdb->prepare( "WHEN {$this->company_name_meta}.meta_value LIKE %s THEN 2 ", $meta_value );
$new_search_orderby .= $wpdb->prepare( "WHEN {$wpdb->posts}.post_content LIKE %s THEN 3 ", $meta_value );
}
$meta_clauses = $query->meta_query->get_clauses();
$meta_clause = $meta_clauses['featured'];
$orderby_array[] = "CAST({$meta_clause['alias']}.meta_value AS {$meta_clause['cast']}) DESC";
if ( ! empty( $new_search_orderby ) ) {
$orderby_array[] = '(CASE ' . $new_search_orderby . 'ELSE 4 END)';
}
if ( isset( $_POST['orderby'] ) && 'title' === sanitize_key( wp_unslash( $_POST['orderby'] ) ) ) {
$orderby = 'post_title';
} else {
$orderby = 'post_date';
}
if ( isset( $_POST['order'] ) && 'ASC' === sanitize_text_field( wp_unslash( $_POST['order'] ) ) ) {
$order = 'ASC';
} else {
$order = 'DESC';
}
// phpcs:enable WordPress.Security.NonceVerification -- already verified here
$orderby_array[] = "{$wpdb->posts}.{$orderby} {$order}";
return implode( ', ', $orderby_array );
}
/**
* AJAX response for getting jobs
*
* @since 1.0
*/
public function get_jobs() {
JB()->ajax()->check_nonce( 'jb-frontend-nonce' );
// phpcs:disable WordPress.Security.NonceVerification -- is verified above
$query_args = array();
$query_args['meta_query'] = array(
'relation' => 'AND',
array(
'relation' => 'OR',
'featured' => array(
'key' => 'jb-featured-order',
'compare' => 'NOT EXISTS',
'type' => 'NUMERIC',
),
array(
'key' => 'jb-featured-order',
'compare' => 'EXISTS',
'type' => 'NUMERIC',
),
),
);
global $wpdb;
// Prepare for BIG SELECT query
$wpdb->query( 'SET SQL_BIG_SELECTS=1' );
/**
* Handle pagination
*
*/
$paged = ! empty( $_POST['page'] ) ? absint( $_POST['page'] ) : 1;
$employer = ! empty( $_POST['employer'] ) ? absint( $_POST['employer'] ) : '';
if ( ! empty( $employer ) ) {
$query_args['author'] = $employer;
}
$statuses = array( 'publish' );
if ( ! empty( $_POST['filled_only'] ) ) {
// show only filled jobs
if ( ! isset( $query_args['meta_query'] ) ) {
$query_args['meta_query'] = array();
}
$query_args['meta_query'] = array_merge(
$query_args['meta_query'],
array(
'relation' => 'AND',
array(
'relation' => 'OR',
array(
'key' => 'jb-is-filled',
'value' => true,
),
array(
'key' => 'jb-is-filled',
'value' => 1,
),
),
)
);
} else {
// regular logic
if ( ! empty( $_POST['hide_filled'] ) ) {
if ( is_user_logged_in() ) {
$employer = get_current_user_id();
$args = array(
'author__not_in' => array( $employer ),
'post_type' => 'jb-job',
'post_status' => array( 'publish', 'draft', 'pending', 'jb-preview', 'jb-expired' ),
'posts_per_page' => -1,
'meta_query' => array(
'relation' => 'OR',
array(
'key' => 'jb-is-filled',
'value' => true,
),
array(
'key' => 'jb-is-filled',
'value' => 1,
),
),
'fields' => 'ids',
);
} else {
$args = array(
'post_type' => 'jb-job',
'post_status' => array( 'publish', 'draft', 'pending', 'jb-preview', 'jb-expired' ),
'posts_per_page' => -1,
'meta_query' => array(
'relation' => 'OR',
array(
'key' => 'jb-is-filled',
'value' => true,
),
array(
'key' => 'jb-is-filled',
'value' => 1,
),
),
'fields' => 'ids',
);
}
$filled_ids = get_posts( $args );
if ( ! empty( $filled_ids ) ) {
$post__not_in = ! empty( $query_args['post__not_in'] ) ? $query_args['post__not_in'] : array();
$query_args['post__not_in'] = array_merge( $post__not_in, $filled_ids );
}
}
if ( empty( $_POST['hide_expired'] ) ) {
$statuses[] = 'jb-expired';
}
}
if ( isset( $_POST['orderby'] ) && 'title' === sanitize_key( wp_unslash( $_POST['orderby'] ) ) ) {
$orderby = 'title';
} else {
$orderby = 'date';
}
if ( isset( $_POST['order'] ) && 'ASC' === sanitize_text_field( wp_unslash( $_POST['order'] ) ) ) {
$order = 'ASC';
} else {
$order = 'DESC';
}
$query_args = array_merge(
$query_args,
array(
'orderby' => array(
'featured' => 'DESC',
$orderby => $order,
),
'post_type' => 'jb-job',
'post_status' => $statuses,
)
);
if ( ! empty( $_POST['get_previous'] ) ) {
// first loading with page > 1....to show the jobs above
$query_args['posts_per_page'] = $this->jobs_per_page * $paged;
$query_args['offset'] = 0;
} else {
$query_args['posts_per_page'] = $this->jobs_per_page;
$query_args['offset'] = $this->jobs_per_page * ( $paged - 1 );
}
if ( ! empty( $_POST['search'] ) ) {
$search = sanitize_text_field( wp_unslash( $_POST['search'] ) );
if ( ! empty( $search ) ) {
$query_args['s'] = $search;
// if search there is 'posts_search_orderby' hook used and order handler is moved to `$this->relevance_search()` function
$query_args['orderby'] = false;
if ( ! isset( $query_args['meta_query'] ) ) {
$query_args['meta_query'] = array();
}
$query_args['meta_query'] = array_merge(
$query_args['meta_query'],
array(
'relation' => 'AND',
array(
'key' => 'jb-company-name',
'value' => $search,
'compare' => 'LIKE',
),
)
);
}
}
if ( ! empty( $_POST['location'] ) ) {
$location = sanitize_text_field( wp_unslash( $_POST['location'] ) );
if ( ! empty( $location ) ) {
if ( ! isset( $query_args['meta_query'] ) ) {
$query_args['meta_query'] = array();
}
$query_args['meta_query'] = array_merge(
$query_args['meta_query'],
array(
'relation' => 'AND',
array(
'relation' => 'OR',
array(
'key' => 'jb-location',
'value' => $location,
'compare' => 'LIKE',
),
array(
'key' => 'jb-location-preferred',
'value' => $location,
'compare' => 'LIKE',
),
),
)
);
}
}
$key = JB()->options()->get( 'googlemaps-api-key' );
if ( ! empty( $key ) ) {
$address_query = array();
if ( ! empty( $_POST['location-city'] ) ) {
$address_query[] = array(
'key' => 'jb-location-city',
'value' => sanitize_text_field( wp_unslash( $_POST['location-city'] ) ),
'compare' => '=',
);
}
if ( ! empty( $_POST['location-state-short'] ) && ! empty( $_POST['location-state-long'] ) ) {
$address_query[] = array(
'relation' => 'OR',
array(
'key' => 'jb-location-state-short',
'value' => sanitize_text_field( wp_unslash( $_POST['location-state-short'] ) ),
'compare' => '=',
),
array(
'key' => 'jb-location-state-long',
'value' => sanitize_text_field( wp_unslash( $_POST['location-state-long'] ) ),
'compare' => '=',
),
);
}
if ( ! empty( $_POST['location-country-short'] ) && ! empty( $_POST['location-country-long'] ) ) {
$address_query[] = array(
'relation' => 'OR',
array(
'key' => 'jb-location-country-short',
'value' => sanitize_text_field( wp_unslash( $_POST['location-country-short'] ) ),
'compare' => '=',
),
array(
'key' => 'jb-location-country-long',
'value' => sanitize_text_field( wp_unslash( $_POST['location-country-long'] ) ),
'compare' => '=',
),
);
}
if ( ! empty( $address_query ) ) {
$address_query['relation'] = 'AND';
if ( ! isset( $query_args['meta_query'] ) ) {
$query_args['meta_query'] = array();
}
$query_args['meta_query'] = array_merge( $query_args['meta_query'], array( $address_query ) );
}
}
$remote_only = ( isset( $_POST['remote_only'] ) && (bool) $_POST['remote_only'] );
if ( $remote_only ) {
if ( ! isset( $query_args['meta_query'] ) ) {
$query_args['meta_query'] = array();
}
$query_args['meta_query'] = array_merge(
$query_args['meta_query'],
array(
'relation' => 'AND',
array(
'key' => 'jb-location-type',
'value' => '1',
'compare' => '=',
),
)
);
}
$types = array();
if ( ! empty( $_POST['type'] ) ) {
$types = array_map( 'absint', array_map( 'trim', explode( ',', wp_unslash( $_POST['type'] ) ) ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- array_map ok
}
if ( ! empty( $types ) ) {
$query_args['tax_query'][] = array(
'taxonomy' => 'jb-job-type',
'field' => 'id',
'terms' => $types,
);
}
if ( JB()->options()->get( 'job-categories' ) ) {
$categories = array();
if ( ! empty( $_POST['category'] ) ) {
$categories = array_map( 'absint', array_map( 'trim', explode( ',', wp_unslash( $_POST['category'] ) ) ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- array_map ok
}
if ( ! empty( $categories ) ) {
$query_args['tax_query'][] = array(
'taxonomy' => 'jb-job-category',
'field' => 'id',
'terms' => $categories,
);
}
}
add_filter( 'get_meta_sql', array( &$this, 'change_meta_sql' ) );
add_filter( 'posts_search', array( &$this, 'set_search' ) );
add_filter( 'posts_where', array( &$this, 'change_where_posts' ) );
add_filter( 'posts_search_orderby', array( &$this, 'relevance_search' ), 10, 2 );
/**
* Filters the WP_Query arguments for getting jobs in the Job List.
*
* @since 1.0
* @hook jb_get_jobs_query_args
*
* @param {array} $query_args Arguments for WP_Query.
*
* @return {array} Arguments for WP_Query.
*/
$query_args = apply_filters( 'jb_get_jobs_query_args', $query_args );
$get_posts = new WP_Query();
$jobs_query = $get_posts->query( $query_args );
remove_filter( 'posts_where', array( &$this, 'change_where_posts' ) );
remove_filter( 'posts_search', array( &$this, 'set_search' ) );
remove_filter( 'get_meta_sql', array( &$this, 'change_meta_sql' ) );
remove_filter( 'posts_search_orderby', array( &$this, 'relevance_search' ) );
/**
* Fires after Jobs List query get results.
*
* @since 1.2.2
* @hook jb_after_get_jobs_query
*
* @param {WP_Query} $jobs_query WP_Query for getting Jobs in the list.
*/
do_action( 'jb_after_get_jobs_query', $jobs_query );
$jobs = array();
if ( ! empty( $jobs_query ) ) {
foreach ( $jobs_query as $job_post ) {
$job_company_data = JB()->common()->job()->get_company_data( $job_post->ID );
$data_types = array();
$types = wp_get_post_terms(
$job_post->ID,
'jb-job-type',
array(
'orderby' => 'name',
'order' => 'ASC',
)
);
foreach ( $types as $type ) {
$data_types[] = array(
'name' => $type->name,
'color' => get_term_meta( $type->term_id, 'jb-color', true ),
'bg_color' => get_term_meta( $type->term_id, 'jb-background', true ),
);
}
$title = esc_html( get_the_title( $job_post ) );
$title = ! empty( $title ) ? $title : esc_html__( '(no title)', 'jobboardwp' );
$job_data = array(
'title' => $title,
'permalink' => get_permalink( $job_post ),
'date' => esc_html( JB()->common()->job()->get_posted_date( $job_post->ID ) ),
'expires' => esc_html( JB()->common()->job()->get_expiry_date( $job_post->ID ) ),
'company' => array(
'name' => esc_html( $job_company_data['name'] ),
'website' => esc_url_raw( $job_company_data['website'] ),
'tagline' => esc_html( $job_company_data['tagline'] ),
'twitter' => esc_html( $job_company_data['twitter'] ),
'facebook' => esc_html( $job_company_data['facebook'] ),
'instagram' => esc_html( $job_company_data['instagram'] ),
),
'logo' => JB()->common()->job()->get_logo( $job_post->ID ),
'location' => wp_kses( JB()->common()->job()->get_location_link( $job_post->ID ), JB()->get_allowed_html( 'templates' ) ),
'types' => $data_types,
'featured' => (bool) JB()->common()->job()->is_featured( $job_post->ID ),
'actions' => array(),
);
if ( JB()->options()->get( 'job-categories' ) ) {
$job_data['category'] = wp_kses( JB()->common()->job()->get_job_category( $job_post->ID ), JB()->get_allowed_html( 'templates' ) );
}
$amount_output = JB()->common()->job()->get_formatted_salary( $job_post->ID );
if ( '' !== $amount_output ) {
$job_data['salary'] = esc_html( $amount_output );
}
/**
* Filters the job data after getting it from WP_Query and prepare it for AJAX response. The referrer is Jobs List shortcode AJAX request.
*
* @since 1.0
* @hook jb_jobs_job_data_response
*
* @param {array} $job_data Job data prepared for AJAX response.
* @param {WP_Post} $job_post Job Post object.
*
* @return {array} Job data prepared for AJAX response.
*/
$jobs[] = apply_filters( 'jb_jobs_job_data_response', $job_data, $job_post );
}
}
$hide_logo = ! empty( $_POST['no_logo'] ) ? (bool) $_POST['no_logo'] : false;
$hide_job_types = ! empty( $_POST['hide_job_types'] ) ? (bool) $_POST['hide_job_types'] : false;
/**
* Filters the AJAX response when getting jobs for the jobs list.
*
* @since 1.0
* @hook jb_jobs_list_response
*
* @param {array} $response AJAX response.
*
* @return {array} AJAX response.
*/
$response = apply_filters(
'jb_jobs_list_response',
array(
'pagination' => $this->calculate_pagination( $get_posts->found_posts ),
'jobs' => $jobs,
'hide_logo' => $hide_logo,
'hide_job_types' => $hide_job_types,
)
);
wp_send_json_success( $response );
// phpcs:enable WordPress.Security.NonceVerification -- already verified here
}
public function count_jobs( $term_id ) {
$hide_filled = JB()->options()->get( 'jobs-list-hide-filled' );
$hide_expired = JB()->options()->get( 'jobs-list-hide-expired' );
$query_args = array(
'post_type' => 'jb-job',
'posts_per_page' => -1,
'meta_query' => array(),
'tax_query' => array(
array(
'taxonomy' => 'jb-job-category',
'field' => 'id',
'terms' => $term_id,
'include_children' => false,
),
),
);
if ( $hide_filled && $hide_expired ) {
$query_args['post_status'] = array( 'publish' );
$query_args['meta_query'] = array_merge(
$query_args['meta_query'],
array(
'relation' => 'AND',
array(
'relation' => 'OR',
array(
'key' => 'jb-is-filled',
'value' => false,
),
array(
'key' => 'jb-is-filled',
'value' => 0,
),
array(
'key' => 'jb-is-filled',
'compare' => 'NOT EXISTS',
),
),
)
);
} elseif ( $hide_filled && ! $hide_expired ) {
$query_args['post_status'] = array(
'publish',
'jb-expired',
);
$query_args['meta_query'] = array_merge(
$query_args['meta_query'],
array(
'relation' => 'AND',
array(
'relation' => 'OR',
array(
'key' => 'jb-is-filled',
'value' => false,
),
array(
'key' => 'jb-is-filled',
'value' => 0,
),
array(
'key' => 'jb-is-filled',
'compare' => 'NOT EXISTS',
),
),
)
);
} elseif ( ! $hide_filled && $hide_expired ) {
$query_args['post_status'] = array( 'publish' );
} else {
$query_args['post_status'] = array(
'publish',
'jb-expired',
);
}
$query = new WP_Query( $query_args );
return $query->found_posts;
}
/**
* Getting Job Categories Tree
*/
public function get_categories() {
JB()->ajax()->check_nonce( 'jb-frontend-nonce' );
/**
* Filters the `get_terms()` arguments when handle AJAX request for getting Job Categories.
*
* @since 1.1.0
* @hook jb_get_job_categories_args
*
* @param {array} $args array of the arguments. See the list of all arguments https://developer.wordpress.org/reference/classes/wp_term_query/__construct/#parameters
*
* @return {array} `get_terms()` arguments.
*/
$args = apply_filters(
'jb_get_job_categories_args',
array(
'taxonomy' => 'jb-job-category',
'hide_empty' => 0,
'get' => 'all',
)
);
$terms = get_terms( $args );
if ( empty( $terms ) || is_wp_error( $terms ) ) {
wp_send_json_error( __( 'Invalid taxonomy registration', 'jobboardwp' ) );
}
if ( is_taxonomy_hierarchical( 'jb-job-category' ) ) {
$children = _get_term_hierarchy( 'jb-job-category' );
$terms = JB()->common()->job()->prepare_categories_options( $terms, $children );
foreach ( $terms as $key => $term ) {
$terms[ $key ]->count = $this->count_jobs( $term->term_id );
$terms[ $key ]->permalink = get_term_link( $term );
}
} else {
foreach ( $terms as $key => $term ) {
$terms[ $key ]->count = $this->count_jobs( $term->term_id );
$terms[ $key ]->level = 0;
$terms[ $key ]->permalink = get_term_link( $term );
}
}
/**
* Filters the AJAX response when getting job categories list.
*
* @since 1.1.0
* @hook jb_get_job_categories_response
*
* @param {array} $response AJAX response.
*
* @return {array} AJAX response.
*/
$response = apply_filters(
'jb_get_job_categories_response',
array(
'terms' => $terms,
'total' => count( $terms ),
)
);
wp_send_json_success( $response );
}
/**
* AJAX handler for job delete
*
* @since 1.0
*/
public function delete_job() {
JB()->ajax()->check_nonce( 'jb-frontend-nonce' );
// phpcs:disable WordPress.Security.NonceVerification -- already verified here
if ( empty( $_POST['job_id'] ) ) {
wp_send_json_error( __( 'Wrong job ID.', 'jobboardwp' ) );
}
$job_id = absint( $_POST['job_id'] );
$job = get_post( $job_id );
if ( is_wp_error( $job ) || empty( $job ) ) {
wp_send_json_error( __( 'Wrong job.', 'jobboardwp' ) );
}
if ( get_current_user_id() !== (int) $job->post_author ) {
wp_send_json_error( __( 'You haven\'t ability to delete this job.', 'jobboardwp' ) );
}
$result = wp_delete_post( $job_id, true );
if ( ! empty( $result ) ) {
/**
* Fires after Job has been deleted.
*
* @since 1.1.0
* @hook jb-after-job-delete
*
* @param {int} $job_id Deleted job ID.
* @param {WP_Post} $post_data The deleted job's post object.
*/
do_action( 'jb-after-job-delete', $job_id, $result ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
wp_send_json_success();
} else {
wp_send_json_error( __( 'Something went wrong.', 'jobboardwp' ) );
}
// phpcs:enable WordPress.Security.NonceVerification -- already verified here
}
/**
* AJAX handler for making a job filled
*
* @since 1.0
*/
public function fill_job() {
JB()->ajax()->check_nonce( 'jb-frontend-nonce' );
// phpcs:disable WordPress.Security.NonceVerification -- already verified here
if ( empty( $_POST['job_id'] ) ) {
wp_send_json_error( __( 'Wrong job ID', 'jobboardwp' ) );
}
$job_id = absint( $_POST['job_id'] );
$job = get_post( $job_id );
if ( is_wp_error( $job ) || empty( $job ) ) {
wp_send_json_error( __( 'Wrong job', 'jobboardwp' ) );
}
if ( get_current_user_id() !== (int) $job->post_author ) {
wp_send_json_error( __( 'You haven\'t ability to fill this job.', 'jobboardwp' ) );
}
if ( JB()->common()->job()->is_filled( $job_id ) ) {
wp_send_json_error( __( 'Job is already filled.', 'jobboardwp' ) );
}
update_post_meta( $job_id, 'jb-is-filled', true );
if ( JB()->common()->job()->is_filled( $job_id ) ) {
$job = get_post( $job_id );
$jobs = array();
$jobs[] = $this->get_job_data( $job );
/**
* Fires after Job has been filled.
*
* @since 1.1.0
* @hook jb_fill_job
*
* @param {int} $job_id Job ID.
* @param {WP_Post} $job The Job's post object.
*/
do_action( 'jb_fill_job', $job_id, $job );
wp_send_json_success( array( 'jobs' => $jobs ) );
} else {
wp_send_json_error( __( 'Something went wrong.', 'jobboardwp' ) );
}
// phpcs:enable WordPress.Security.NonceVerification -- already verified here
}
/**
* AJAX handler for making a job unfilled
*
* @since 1.0
*/
public function unfill_job() {
JB()->ajax()->check_nonce( 'jb-frontend-nonce' );
// phpcs:disable WordPress.Security.NonceVerification -- already verified here
if ( empty( $_POST['job_id'] ) ) {
wp_send_json_error( __( 'Wrong job ID', 'jobboardwp' ) );
}
$job_id = absint( $_POST['job_id'] );
$job = get_post( $job_id );
if ( is_wp_error( $job ) || empty( $job ) ) {
wp_send_json_error( __( 'Wrong job', 'jobboardwp' ) );
}
if ( get_current_user_id() !== (int) $job->post_author ) {
wp_send_json_error( __( 'You haven\'t ability to un-fill this job.', 'jobboardwp' ) );
}
if ( ! JB()->common()->job()->is_filled( $job_id ) ) {
wp_send_json_error( __( 'Job isn\'t filled yet.', 'jobboardwp' ) );
}
update_post_meta( $job_id, 'jb-is-filled', false );
if ( ! JB()->common()->job()->is_filled( $job_id ) ) {
$job = get_post( $job_id );
$jobs = array();
$jobs[] = $this->get_job_data( $job );
/**
* Fires after Job has been unfilled.
*
* @since 1.1.0
* @hook jb_unfill_job
*
* @param {int} $job_id Job ID.
* @param {WP_Post} $job The Job's post object.
*/
do_action( 'jb_unfill_job', $job_id, $job );
wp_send_json_success( array( 'jobs' => $jobs ) );
} else {
wp_send_json_error( __( 'Something went wrong.', 'jobboardwp' ) );
}
// phpcs:enable WordPress.Security.NonceVerification -- already verified here
}
/**
* Prepare job data for AJAX response
*
* @param \WP_Post $job_post
*
* @return array
*
* @since 1.0
*/
public function get_job_data( $job_post ) {
if ( 'publish' !== $job_post->post_status ) {
$status_label = JB()->common()->job()->get_status( $job_post->ID );
$status = 'jb-preview' === $job_post->post_status ? 'draft' : $job_post->post_status;
} else {
$status_label = JB()->common()->job()->is_filled( $job_post->ID ) ? __( 'Filled', 'jobboardwp' ) : __( 'Not-filled', 'jobboardwp' );
$status = JB()->common()->job()->is_filled( $job_post->ID ) ? 'filled' : 'not-filled';
}
$title = esc_html( get_the_title( $job_post ) );
$title = ! empty( $title ) ? $title : esc_html__( '(no title)', 'jobboardwp' );
/**
* Filters the job data after getting it from WP_Query and prepare it for AJAX response. The referrer is Jobs Dashboard shortcode AJAX request.
*
* @since 1.0
* @hook jb_job_dashboard_job_data_response
*
* @param {array} $job_data Job data prepared for AJAX response.
* @param {WP_Post} $job_post Job Post object.
*
* @return {array} Job data prepared for AJAX response.
*/
return apply_filters(
'jb_job_dashboard_job_data_response',
array(
'id' => $job_post->ID,
'title' => $title,
'permalink' => get_permalink( $job_post ),
'is_published' => 'publish' === $job_post->post_status,
'status_label' => $status_label,
'status' => $status,
'date' => esc_html( JB()->common()->job()->get_posted_date( $job_post->ID ) ),
'expires' => esc_html( JB()->common()->job()->get_expiry_date( $job_post->ID ) ),
'actions' => JB()->common()->job()->get_actions( $job_post->ID ),
),
$job_post
);
}
/**
* AJAX handler for getting employer's jobs
*
* @since 1.0
*/
public function get_employer_jobs() {
JB()->ajax()->check_nonce( 'jb-frontend-nonce' );
$employer = get_current_user_id();
$get_posts = new WP_Query();
$args = array(
'author' => $employer,
'orderby' => 'date',
'order' => 'DESC',
'post_type' => 'jb-job',
'post_status' => array( 'publish', 'draft', 'pending', 'jb-preview', 'jb-expired' ),
'posts_per_page' => -1,
);
/**
* Filters the WP_Query arguments for getting jobs in the Jobs Dashboard.
*
* @since 1.0
* @hook jb_get_employer_jobs_args
*
* @param {array} $args Arguments for WP_Query.
*
* @return {array} Arguments for WP_Query.
*/
$args = apply_filters( 'jb_get_employer_jobs_args', $args );
$jobs_query = $get_posts->query( $args );
$jobs = array();
if ( ! empty( $jobs_query ) ) {
foreach ( $jobs_query as $job_post ) {
$jobs[] = $this->get_job_data( $job_post );
}
}
/**
* Filters the AJAX response when getting jobs list in jobs dashboard.
*
* @since 1.1.0
* @hook jb_job_dashboard_response
*
* @param {array} $response AJAX response.
*
* @return {array} AJAX response.
*/
$response = apply_filters(
'jb_job_dashboard_response',
array(
'jobs' => $jobs,
)
);
wp_send_json_success( $response );
}
/**
* Get data array for pagination
*
* @param int $total_jobs
*
* @return array
*
* @since 1.0
*/
public function calculate_pagination( $total_jobs ) {
// phpcs:disable WordPress.Security.NonceVerification -- already verified here
$current_page = ! empty( $_POST['page'] ) ? absint( $_POST['page'] ) : 1;
$total_pages = ceil( $total_jobs / $this->jobs_per_page );
if ( ! empty( $total_pages ) ) {
$index1 = 0 - ( $current_page - 2 ) + 1;
$to = $current_page + 2;
if ( $index1 > 0 ) {
$to += $index1;
}
$index2 = $total_pages - ( $current_page + 2 );
$from = $current_page - 2;
if ( $index2 < 0 ) {
$from += $index2;
}
$pages_to_show = range(
( $from > 0 ) ? $from : 1,
( $to <= $total_pages ) ? $to : $total_pages
);
}
/**
* Filters the pagination results for the jobs list.
*
* @since 1.1.1
* @hook jb_jobs_list_calculate_pagination_result
*
* @param {array} $result Pagination results.
*
* @return {array} Pagination results.
*/
return apply_filters(
'jb_jobs_list_calculate_pagination_result',
array(
'pages_to_show' => ( ! empty( $pages_to_show ) && count( $pages_to_show ) > 1 ) ? array_values( $pages_to_show ) : array(),
'current_page' => $current_page,
'total_pages' => $total_pages,
'total_jobs' => $total_jobs,
)
);
// phpcs:enable WordPress.Security.NonceVerification -- already verified here
}
/**
* AJAX handler for validate job data on save through wp-admin editor
*
* @since 1.0
*/
public function validate_job() {
JB()->ajax()->check_nonce( 'jb-backend-nonce' );
// phpcs:disable WordPress.Security.NonceVerification -- already verified here
if ( empty( $_POST['data'] ) ) {
wp_send_json_error( __( 'Wrong Data', 'jobboardwp' ) );
}
$errors = array();
if ( empty( $_POST['description'] ) ) {
$errors['empty'][] = 'description';
} else {
$description = wp_kses_post( wp_unslash( $_POST['description'] ) );
if ( empty( $description ) ) {
$errors['empty'][] = 'description';
}
}
if ( empty( $_POST['data']['jb-application-contact'] ) ) {
$errors['empty'][] = 'jb-application-contact';
} else {
$method = JB()->options()->get( 'application-method' );
if ( 'email' === $method ) {
$app_contact = sanitize_email( wp_unslash( $_POST['data']['jb-application-contact'] ) );
if ( ! is_email( $app_contact ) ) {
$errors['wrong'][] = 'jb-application-contact';
}
} elseif ( 'url' === $method ) {
$app_contact = sanitize_text_field( wp_unslash( $_POST['data']['jb-application-contact'] ) );
if ( false === strpos( $app_contact, 'http:' ) && false === strpos( $app_contact, 'https:' ) ) {
$app_contact = 'https://' . $app_contact;
}
if ( is_email( $app_contact ) || ! JB()->common()->job()->validate_url( $app_contact ) ) {
$errors['wrong'][] = 'jb-application-contact';
}
} else {
$app_contact = sanitize_email( wp_unslash( $_POST['data']['jb-application-contact'] ) );
if ( ! is_email( $app_contact ) ) {
$app_contact = sanitize_text_field( wp_unslash( $_POST['data']['jb-application-contact'] ) );
// Prefix http if needed.
if ( false === strpos( $app_contact, 'http:' ) && false === strpos( $app_contact, 'https:' ) ) {
$app_contact = 'https://' . $app_contact;
}
}
if ( ! is_email( $app_contact ) && ! JB()->common()->job()->validate_url( $app_contact ) ) {
$errors['wrong'][] = 'jb-application-contact';
}
}
}
if ( ! isset( $_POST['data']['jb-location-type'] ) ) {
$errors['wrong'][] = 'jb-location-type';
} else {
$location_type = sanitize_text_field( wp_unslash( $_POST['data']['jb-location-type'] ) );
if ( '0' === $location_type ) {
if ( empty( $_POST['data']['jb-location'] ) ) {
$errors['empty'][] = 'jb-location';
} else {
$location = sanitize_text_field( wp_unslash( $_POST['data']['jb-location'] ) );
if ( empty( $location ) ) {
$errors['empty'][] = 'jb-location';
}
}
}
}
if ( empty( $_POST['data']['jb-company-name'] ) ) {
$errors['empty'][] = 'jb-company-name';
} else {
$company_name = sanitize_text_field( wp_unslash( $_POST['data']['jb-company-name'] ) );
if ( empty( $company_name ) ) {
$errors['empty'][] = 'jb-company-name';
}
}
if ( JB()->options()->get( 'required-job-type' ) ) {
if ( empty( $_POST['data']['jb-job-type'] ) ) {
$errors['empty'][] = 'jb-job-type';
} else {
$job_type = absint( $_POST['data']['jb-job-type'] );
if ( empty( $job_type ) ) {
$errors['empty'][] = 'jb-job-type';
}
}
}
if ( JB()->options()->get( 'job-salary' ) ) {
if ( empty( $_POST['data']['jb-salary-type'] ) && JB()->options()->get( 'required-job-salary' ) ) {
$errors['empty'][] = 'jb-salary-type';
}
if ( ! empty( $_POST['data']['jb-salary-type'] ) ) {
if ( empty( $_POST['data']['jb-salary-amount-type'] ) ) {
$errors['empty'][] = 'jb-salary-amount-type';
} elseif ( 'numeric' === $_POST['data']['jb-salary-amount-type'] ) {
if ( empty( $_POST['data']['jb-salary-amount'] ) ) {
$errors['empty'][] = 'jb-salary-amount';
} elseif ( ! is_numeric( $_POST['data']['jb-salary-amount'] ) ) {
$errors['wrong'][] = 'jb-salary-amount';
}
} elseif ( 'range' === $_POST['data']['jb-salary-amount-type'] ) {
if ( empty( $_POST['data']['jb-salary-min-amount'] ) && empty( $_POST['data']['jb-salary-max-amount'] ) ) {
$errors['empty'][] = 'jb-salary-min-amount';
} else {
if ( ! is_numeric( $_POST['data']['jb-salary-min-amount'] ) ) {
$errors['wrong'][] = 'jb-salary-min-amount';
} elseif ( 0 !== absint( $_POST['data']['jb-salary-max-amount'] ) && absint( $_POST['data']['jb-salary-min-amount'] ) >= absint( $_POST['data']['jb-salary-max-amount'] ) ) {
$errors['wrong'][] = 'jb-salary-min-amount';
}
if ( ! is_numeric( $_POST['data']['jb-salary-max-amount'] ) ) {
$errors['wrong'][] = 'jb-salary-max-amount';
} elseif ( 0 !== absint( $_POST['data']['jb-salary-max-amount'] ) && absint( $_POST['data']['jb-salary-max-amount'] ) <= absint( $_POST['data']['jb-salary-min-amount'] ) ) {
$errors['wrong'][] = 'jb-salary-max-amount';
}
}
}
if ( 'recurring' === $_POST['data']['jb-salary-type'] && empty( $_POST['data']['jb-salary-period'] ) ) {
$errors['empty'][] = 'jb-salary-period';
}
}
}
/**
* Filters job post validation errors.
*
* Note: You may use this hook for adding your custom validations or remove existed while job saved through wp-admin editor.
*
* Format for the errors: key = 'empty' - required field is empty
* key = 'wrong' - invalid format if the field
* value = array( '{field_id}' ) - user field ID that you used for the registration the field on the form.
*
* @since 1.2.2
* @hook jb_ajax_job_validation_errors
*
* @param {array} $errors Errors list. If there aren't any errors it's empty array.
*
* @return {array} Errors list.
*/
$errors = apply_filters( 'jb_ajax_job_validation_errors', $errors );
if ( ! empty( $errors ) ) {
// add notice text
$errors['notice'][] = __( 'Wrong Job\'s data', 'jobboardwp' );
if ( empty( $description ) ) {
$errors['notice'][] = __( ' Description is required', 'jobboardwp' );
}
wp_send_json_success( $errors );
} else {
wp_send_json_success( array( 'valid' => 1 ) );
}
// phpcs:enable WordPress.Security.NonceVerification
}
}
}