<?php
namespace jb\common;
use WP_Filesystem_Base;
use WP_Post;
use function WP_Filesystem;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
if ( ! class_exists( 'jb\common\Job' ) ) {
/**
* Class Job
*
* @package jb\common
*/
class Job {
/**
* Render job types layout
*
* @param int $job_id
*
* @return string
*
* @since 1.0
*/
public function display_types( $job_id ) {
$types = wp_get_post_terms(
$job_id,
'jb-job-type',
array(
'orderby' => 'name',
'order' => 'ASC',
)
);
if ( empty( $types ) || is_wp_error( $types ) ) {
return '';
}
ob_start();
foreach ( $types as $type ) {
$term_color = get_term_meta( $type->term_id, 'jb-color', true );
$term_background = get_term_meta( $type->term_id, 'jb-background', true );
$attr = '';
if ( ! empty( $term_color ) || ! empty( $term_background ) ) {
$attr .= 'style="';
if ( ! empty( $term_color ) ) {
$attr .= 'color:' . esc_attr( $term_color ) . ';';
}
if ( ! empty( $term_background ) ) {
$attr .= 'background:' . esc_attr( $term_background ) . ';';
}
$attr .= '"';
}
echo wp_kses( '<div class="jb-job-type" ' . $attr . '>' . esc_html( $type->name ) . '</div>', JB()->get_allowed_html( 'templates' ) );
}
return ob_get_clean();
}
/**
* Calculates and returns the job expiry date.
*
* @return string
*
* @since 1.0
*/
public function calculate_expiry() {
$duration = absint( JB()->options()->get( 'job-duration' ) );
if ( ! empty( $duration ) ) {
return gmdate( 'Y-m-d', strtotime( "+{$duration} days" ) );
}
return '';
}
/**
* Returns the job expiry date.
*
* @param int $job_id Job post ID
*
* @return string
*
* @since 1.0
*/
public function get_expiry_date( $job_id ) {
$expiry_date = get_post_meta( $job_id, 'jb-expiry-date', true );
if ( empty( $expiry_date ) ) {
return '';
}
return date_i18n( get_option( 'date_format' ), strtotime( $expiry_date ) );
}
/**
* Returns the job's raw expiry date.
*
* @param int $job_id Job post ID
*
* @return string
*
* @since 1.0
*/
public function get_expiry_date_raw( $job_id ) {
$expiry_date = get_post_meta( $job_id, 'jb-expiry-date', true );
if ( empty( $expiry_date ) ) {
return '';
}
return $expiry_date;
}
/**
* Returns the job posted date.
*
* @param int $job_id Job post ID
*
* @return string
*
* @since 1.0
*/
public function get_posted_date( $job_id ) {
$posted_date = '';
if ( JB()->is_request( 'admin' ) && ! JB()->is_request( 'ajax' ) ) {
$posted_date = get_post_time( get_option( 'date_format' ), false, $job_id, true );
} else {
$dateformat = JB()->options()->get( 'job-dateformat' );
if ( 'relative' === $dateformat ) {
$posted_date = human_time_diff( get_post_timestamp( $job_id ) );
} elseif ( 'default' === $dateformat ) {
$posted_date = get_post_time( get_option( 'date_format' ), false, $job_id, true );
}
}
return $posted_date;
}
/**
* Returns the job expiry date.
*
* @param int $job_id Job post ID
*
* @return string
*
* @since 1.0
*/
public function get_html_datetime( $job_id ) {
return get_post_time( 'c', false, $job_id, true );
}
/**
* Returns the job category.
*
* @param int $job_id Job post ID
*
* @return string
*
* @since 1.1.1
*/
public function get_job_category( $job_id ) {
if ( ! JB()->options()->get( 'job-categories' ) ) {
return '';
}
$terms = get_the_terms( $job_id, 'jb-job-category' );
if ( empty( $terms ) ) {
return '';
}
return '<i class="fas fa-list-alt"></i><a href="' . esc_url( get_term_link( $terms[0]->term_id, 'jb-job-category' ) ) . '">' . esc_html( $terms[0]->name ) . '</a>';
}
/**
* Returns the job author.
*
* @param int $job_id Job post ID
*
* @return string
*
* @since 1.0
*/
public function get_job_author( $job_id ) {
$job = get_post( $job_id );
$author = get_userdata( $job->post_author );
if ( empty( $author ) ) {
return __( 'Guest', 'jobboardwp' );
}
return $author->display_name;
}
/**
* Returns the job location type.
*
* @param int $job_id Job post ID
* @param bool $raw RAW or formatted location
*
* @return string
*
* @since 1.0
*/
public function get_location_type( $job_id, $raw = false ) {
$location = get_post_meta( $job_id, 'jb-location-type', true );
if ( $raw ) {
return $location;
}
switch ( $location ) {
case '':
$location = __( 'Onsite or remote', 'jobboardwp' );
break;
case '0':
$location = __( 'Onsite', 'jobboardwp' );
break;
case '1':
$location = __( 'Remote', 'jobboardwp' );
break;
}
return $location;
}
/**
* Returns the job location data.
*
* @param int $job_id Job post ID
*
* @return string
*
* @since 1.0
*/
public function get_location_data( $job_id ) {
$location_data = get_post_meta( $job_id, 'jb-location-raw-data', true );
return ! empty( $location_data ) ? $location_data : '';
}
/**
* Returns the job location.
*
* @param int $job_id Job post ID
* @param bool $raw RAW or formatted location
*
* @return string
*
* @since 1.0
*/
public function get_location( $job_id, $raw = false ) {
$location = get_post_meta( $job_id, 'jb-location', true );
if ( $raw ) {
return $location;
}
$location_type = get_post_meta( $job_id, 'jb-location-type', true );
if ( '1' === $location_type && empty( $location ) ) {
return __( 'Remote', 'jobboardwp' );
}
if ( empty( $location ) ) {
return __( 'Anywhere', 'jobboardwp' );
}
return $location;
}
/**
* Get location link
*
* @param int $job_id
*
* @return string
*
* @since 1.0
*/
public function get_location_link( $job_id ) {
$location_raw = JB()->common()->job()->get_location( $job_id, true );
$type_raw = $this->get_location_type( $job_id, true );
$type = $this->get_location_type( $job_id );
if ( '1' === $type_raw ) {
if ( empty( $location_raw ) ) {
return esc_html__( 'Remote', 'jobboardwp' );
}
$location = JB()->common()->job()->get_location( $job_id );
if ( empty( $location ) ) {
return '';
}
$location = '<a href="https://maps.google.com/maps?q=' . rawurlencode( wp_strip_all_tags( $location ) ) . '&zoom=14&size=512x512&maptype=roadmap&sensor=false" target="_blank">' . $location . '</a>';
// translators: %1$s is a location type; %2$s is a location.
return sprintf( __( '%1$s (%2$s)', 'jobboardwp' ), $type, $location );
}
if ( empty( $location_raw ) ) {
return esc_html__( 'Anywhere', 'jobboardwp' );
}
$location = JB()->common()->job()->get_location( $job_id );
if ( empty( $location ) ) {
return '';
}
return '<a href="https://maps.google.com/maps?q=' . rawurlencode( wp_strip_all_tags( $location ) ) . '&zoom=14&size=512x512&maptype=roadmap&sensor=false" target="_blank">' . $location . '</a>';
}
/**
* Returns the job company.
*
* @param int $job_id Job post ID
*
* @return string
*
* @since 1.0
*/
public function get_company( $job_id ) {
$company_name = get_post_meta( $job_id, 'jb-company-name', true );
$company_website = get_post_meta( $job_id, 'jb-company-website', true );
$company_tagline = get_post_meta( $job_id, 'jb-company-tagline', true );
if ( empty( $company_name ) ) {
return '';
}
if ( ! empty( $company_website ) ) {
$company = sprintf( '<span title="%s"><a href="%s">%s</a></span>', $company_tagline, $company_website, $company_name );
} else {
$company = sprintf( '<span title="%s">%s</span>', $company_tagline, $company_name );
}
return $company;
}
/**
* Build job's company data
*
* @param int $job_id
*
* @return array
*
* @since 1.0
*/
public function get_company_data( $job_id ) {
$company_name = get_post_meta( $job_id, 'jb-company-name', true );
$company_website = get_post_meta( $job_id, 'jb-company-website', true );
$company_tagline = get_post_meta( $job_id, 'jb-company-tagline', true );
$company_twitter = get_post_meta( $job_id, 'jb-company-twitter', true );
$company_facebook = get_post_meta( $job_id, 'jb-company-facebook', true );
$company_instagram = get_post_meta( $job_id, 'jb-company-instagram', true );
/**
* Filters the company data.
*
* @since 1.1.0
* @hook jb-job-company-data
*
* @param {array} $company_data Job's company data.
* @param {int} $job_id Job ID passed into the function.
*
* @return {array} Maybe modified job's company data.
*/
return apply_filters(
'jb-job-company-data', // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
array(
'name' => $company_name,
'website' => $company_website,
'tagline' => $company_tagline,
'twitter' => $company_twitter,
'facebook' => $company_facebook,
'instagram' => $company_instagram,
),
$job_id
);
}
/**
* Get job logo
*
* @param int $job_id
* @param bool $raw
*
* @return string
*
* @since 1.0
*/
public function get_logo( $job_id, $raw = false ) {
if ( $raw ) {
$company_logo = '';
$attachment_id = get_post_thumbnail_id( $job_id );
if ( $attachment_id ) {
$image = wp_get_attachment_image_src( $attachment_id );
$company_logo = isset( $image[0] ) ? $image[0] : '';
}
/**
* Filters the job logo.
*
* @since 1.2.2
* @hook jb_job_logo
*
* @param {string} $logo Job logo.
* @param {int} $job_id Job ID.
* @param {bool} $raw Context for getting job logo. If `true` getting RAW link to the logo.
*
* @return {array} Job logo.
*/
return apply_filters( 'jb_job_logo', $company_logo, $job_id, $raw );
}
$company_logo = get_the_post_thumbnail( $job_id, 'thumbnail', array( 'class' => 'jb-job-company-logo' ) );
if ( ! empty( $company_logo ) ) {
$company_logo = '<div class="jb-job-company-logo-wrapper">' . $company_logo . '</div>';
} else {
$company_logo = '';
}
/** This filter is documented in includes/common/class-job.php */
return apply_filters( 'jb_job_logo', $company_logo, $job_id, $raw );
}
/**
* Returns the job status.
*
* @param int $job_id Job post ID
*
* @return string
*
* @since 1.0
*/
public function get_status( $job_id ) {
$job = get_post( $job_id );
if ( empty( $job->post_status ) ) {
return '';
}
if ( 'jb-preview' === $job->post_status ) {
$job->post_status = 'draft';
}
$post_status = get_post_status_object( $job->post_status );
if ( null === $post_status ) {
return '';
}
return ! empty( $post_status->label ) ? $post_status->label : '';
}
/**
* Is job filled?
*
* @param int $job_id
*
* @return bool
*
* @since 1.0
*/
public function is_filled( $job_id ) {
$filled = get_post_meta( $job_id, 'jb-is-filled', true );
return (bool) $filled;
}
/**
* Is job featured?
*
* @param int $job_id
*
* @return bool
*
* @since 1.2.4
*/
public function is_featured( $job_id ) {
$featured = get_post_meta( $job_id, 'jb-is-featured', true );
return (bool) $featured;
}
/**
* Is job expired?
*
* @param int $job_id
*
* @return bool
*
* @since 1.0
*/
public function is_expired( $job_id ) {
$job = get_post( $job_id );
if ( empty( $job ) || is_wp_error( $job ) ) {
return false;
}
if ( 'jb-expired' === $job->post_status ) {
return true;
}
return false;
}
/**
* Can job be applied?
*
* @param int $job_id
*
* @return bool
*
* @since 1.0
*/
public function can_applied( $job_id ) {
$job = get_post( $job_id );
$can_applied = false;
if ( empty( $job ) || is_wp_error( $job ) ) {
return false;
}
if ( ! $this->is_filled( $job_id ) && ! in_array( $job->post_status, array( 'jb-preview', 'jb-expired' ), true ) ) {
$can_applied = true;
}
/**
* Filters the ability of the job can be applied.
*
* @since 1.0
* @hook jb_can_applied_job
*
* @param {bool} $can_applied Can a job be applied? Set it to the `true` if a job can be applied.
* @param {int} $job_id Job ID passed into the function.
*
* @return {bool} Can a job be applied?
*/
return apply_filters( 'jb_can_applied_job', $can_applied, $job_id );
}
/**
* Getting formatted job salary.
*
* @param int $job_id Job ID.
*
* @return string Formatted salary string. Empty string in the case when invalid salary data in meta
*/
public function get_formatted_salary( $job_id ) {
$amount_output = '';
if ( ! JB()->options()->get( 'job-salary' ) ) {
return $amount_output;
}
$salary_type = get_post_meta( $job_id, 'jb-salary-type', true );
if ( '' === $salary_type ) {
return $amount_output;
}
$currency = JB()->options()->get( 'job-salary-currency' );
$currency_symbols = JB()->config()->get( 'currencies' );
$currency_symbol = $currency_symbols[ $currency ]['symbol'];
$salary_amount_type = get_post_meta( $job_id, 'jb-salary-amount-type', true );
if ( 'numeric' === $salary_amount_type ) {
$salary_amount = get_post_meta( $job_id, 'jb-salary-amount', true );
if ( empty( $salary_amount ) ) {
return $amount_output;
}
$amount_output = sprintf( JB()->get_job_salary_format(), $currency_symbol, $salary_amount );
} else {
$salary_min_amount = get_post_meta( $job_id, 'jb-salary-min-amount', true );
$salary_max_amount = get_post_meta( $job_id, 'jb-salary-max-amount', true );
if ( empty( $salary_min_amount ) && empty( $salary_max_amount ) ) {
return $amount_output;
}
if ( empty( $salary_min_amount ) && ! empty( $salary_max_amount ) ) {
$amount = sprintf( JB()->get_job_salary_format(), $currency_symbol, $salary_max_amount );
// translators: %s is maximum job salary amount.
$amount_output = sprintf( __( 'Up to %s', 'jobboardwp' ), $amount );
} elseif ( ! empty( $salary_min_amount ) && empty( $salary_max_amount ) ) {
$amount = sprintf( JB()->get_job_salary_format(), $currency_symbol, $salary_min_amount );
// translators: %s is minimum job salary amount.
$amount_output = sprintf( __( 'Starts from %s', 'jobboardwp' ), $amount );
} else {
$amount_output = sprintf( JB()->get_job_salary_format(), $currency_symbol, $salary_min_amount . '-' . $salary_max_amount );
}
}
if ( 'recurring' === $salary_type ) {
$salary_period = get_post_meta( $job_id, 'jb-salary-period', true );
if ( empty( $salary_period ) ) {
return $amount_output;
}
// translators: %1$s is a job's salary amount or range; %2$s is a job's salary period.
$amount_output = sprintf( __( '%1$s per %2$s', 'jobboardwp' ), $amount_output, $salary_period );
}
return $amount_output;
}
/**
* @return int
*/
public function get_maximum_salary() {
global $wpdb;
$max_values = $wpdb->get_results(
"SELECT DISTINCT meta_value
FROM {$wpdb->postmeta}
WHERE meta_key = 'jb-salary-max-amount' OR
meta_key = 'jb-salary-amount'",
ARRAY_A
);
$max_value = 0;
foreach ( $max_values as $value ) {
if ( null !== $value['meta_value'] && ( 0 === $max_value || $max_value < $value['meta_value'] ) ) {
$max_value = absint( $value['meta_value'] );
}
}
return $max_value;
}
/**
* Get job RAW data
*
* @param int $job_id
*
* @return array|bool
*
* @since 1.0
*/
public function get_raw_data( $job_id ) {
$job = get_post( $job_id );
if ( empty( $job ) || is_wp_error( $job ) ) {
return false;
}
$company_name = get_post_meta( $job_id, 'jb-company-name', true );
$company_website = get_post_meta( $job_id, 'jb-company-website', true );
$company_tagline = get_post_meta( $job_id, 'jb-company-tagline', true );
$company_twitter = get_post_meta( $job_id, 'jb-company-twitter', true );
$company_facebook = get_post_meta( $job_id, 'jb-company-facebook', true );
$company_instagram = get_post_meta( $job_id, 'jb-company-instagram', true );
$company_logo = '';
$attachment_id = get_post_thumbnail_id( $job_id );
if ( $attachment_id ) {
$image = wp_get_attachment_image_src( $attachment_id, 'thumbnail' );
$company_logo = isset( $image[0] ) ? $image[0] : '';
}
$types = wp_get_post_terms(
$job_id,
'jb-job-type',
array(
'orderby' => 'name',
'order' => 'ASC',
'fields' => 'ids',
)
);
if ( empty( $types ) || is_wp_error( $types ) ) {
$job_types = array();
} else {
$job_types = $types;
}
$response = array(
'title' => $job->post_title,
'description' => $job->post_content,
'type' => $job_types,
'location' => $this->get_location( $job_id, true ),
'location_type' => $this->get_location_type( $job_id, true ),
'location_data' => $this->get_location_data( $job_id ),
'app_contact' => get_post_meta( $job_id, 'jb-application-contact', true ),
'expires' => get_post_meta( $job_id, 'jb-expiry-date', true ),
'company_name' => $company_name,
'company_website' => $company_website,
'company_tagline' => $company_tagline,
'company_twitter' => $company_twitter,
'company_facebook' => $company_facebook,
'company_instagram' => $company_instagram,
'company_logo' => $company_logo,
);
if ( JB()->options()->get( 'job-categories' ) ) {
$categories = wp_get_post_terms(
$job_id,
'jb-job-category',
array(
'orderby' => 'name',
'order' => 'ASC',
'fields' => 'ids',
)
);
if ( empty( $categories ) || is_wp_error( $categories ) ) {
$job_categories = array();
} else {
$job_categories = $categories;
}
$response['category'] = $job_categories;
}
if ( JB()->options()->get( 'job-salary' ) ) {
if ( get_post_meta( $job_id, 'jb-salary-type', true ) ) {
$response['salary_type'] = get_post_meta( $job_id, 'jb-salary-type', true );
}
if ( get_post_meta( $job_id, 'jb-salary-amount-type', true ) ) {
$response['salary_amount_type'] = get_post_meta( $job_id, 'jb-salary-amount-type', true );
}
if ( get_post_meta( $job_id, 'jb-salary-amount', true ) ) {
$response['salary_amount'] = get_post_meta( $job_id, 'jb-salary-amount', true );
}
if ( get_post_meta( $job_id, 'jb-salary-min-amount', true ) ) {
$response['salary_min_amount'] = get_post_meta( $job_id, 'jb-salary-min-amount', true );
}
if ( get_post_meta( $job_id, 'jb-salary-max-amount', true ) ) {
$response['salary_max_amount'] = get_post_meta( $job_id, 'jb-salary-max-amount', true );
}
if ( get_post_meta( $job_id, 'jb-salary-period', true ) ) {
$response['salary_period'] = get_post_meta( $job_id, 'jb-salary-period', true );
}
}
/**
* Filters the job raw data.
*
* @since 1.0
* @hook jb-job-raw-data
*
* @param {array} $response Job's raw data.
* @param {int} $job_id Job ID passed into the function.
*
* @return {array} Job data in raw format.
*/
return apply_filters( 'jb-job-raw-data', $response, $job_id ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
}
/**
* Get job actions
*
* @param int|WP_Post|array $job
*
* @return array
*
* @since 1.0
*/
public function get_actions( $job ) {
if ( is_numeric( $job ) ) {
$job = get_post( $job );
}
$actions = array();
if ( 'jb-expired' === $job->post_status ) {
$actions = array_merge(
$actions,
array(
'edit' => array(
'href' => $this->get_edit_link( $job->ID ),
'title' => __( 'Submit again', 'jobboardwp' ),
),
'delete' => array(
'title' => __( 'Delete', 'jobboardwp' ),
),
)
);
}
if ( in_array( $job->post_status, array( 'draft', 'jb-preview' ), true ) ) {
$actions = array_merge(
$actions,
array(
'edit' => array(
'href' => $this->get_edit_link( $job->ID ),
'title' => __( 'Continue submission', 'jobboardwp' ),
),
'delete' => array(
'title' => __( 'Delete', 'jobboardwp' ),
),
)
);
}
if ( 'pending' === $job->post_status && JB()->options()->get( 'pending-job-editing' ) ) {
$actions['edit'] = array(
'href' => $this->get_edit_link( $job->ID ),
'title' => __( 'Edit', 'jobboardwp' ),
);
}
if ( 'publish' === $job->post_status ) {
if ( 0 !== (int) JB()->options()->get( 'published-job-editing' ) ) {
$actions['edit'] = array(
'href' => $this->get_edit_link( $job->ID ),
'title' => __( 'Edit', 'jobboardwp' ),
);
}
if ( ! $this->is_filled( $job->ID ) ) {
$actions['fill'] = array(
'title' => __( 'Mark as filled', 'jobboardwp' ),
);
} else {
$actions['un-fill'] = array(
'title' => __( 'Mark as un-filled', 'jobboardwp' ),
);
}
$actions['delete'] = array(
'title' => __( 'Delete', 'jobboardwp' ),
);
}
return $actions;
}
/**
* Get job preview link
*
* @param $job_id
*
* @return string
*
* @since 1.0
*/
public function get_preview_link( $job_id ) {
$current_url = JB()->get_current_url( true );
return add_query_arg(
array(
'jb-preview' => 1,
'job-id' => $job_id,
'nonce' => wp_create_nonce( 'jb-job-preview' . $job_id ),
),
$current_url
);
}
/**
* Get job edit link
*
* @param int $job_id
* @param null|string $base_url
*
* @return string
*
* @since 1.0
*/
public function get_edit_link( $job_id, $base_url = null ) {
if ( empty( $base_url ) ) {
$post_job_page = JB()->common()->permalinks()->get_predefined_page_link( 'job-post' );
} else {
$post_job_page = $base_url;
}
return add_query_arg(
array(
'job-id' => $job_id,
'nonce' => wp_create_nonce( 'jb-job-draft' . $job_id ),
),
$post_job_page
);
}
/**
* Get job's structured data for schema.org
*
* @param int|WP_Post|array $job
*
* @return array|bool
*
* @since 1.0
*/
public function get_structured_data( $job ) {
if ( is_numeric( $job ) ) {
$job = get_post( $job );
}
$data = array();
$data['@context'] = 'https://schema.org/';
$data['@type'] = 'JobPosting';
$data['datePosted'] = get_post_time( 'c', false, $job );
$job_expires = get_post_meta( $job->ID, 'jb-expiry-date', true );
if ( ! empty( $job_expires ) ) {
$data['validThrough'] = gmdate( 'c', strtotime( $job_expires ) );
}
$data['title'] = wp_strip_all_tags( get_the_title( $job->ID ) );
$data['description'] = get_the_content( $job->ID );
$types = wp_get_post_terms(
$job->ID,
'jb-job-type',
array(
'orderby' => 'name',
'order' => 'ASC',
)
);
if ( ! empty( $types ) && ! is_wp_error( $types ) ) {
$employment_types = array();
foreach ( $types as $type ) {
$employment_types[] = $type->name;
}
$data['employmentType'] = esc_html( implode( ', ', $employment_types ) );
}
$logo = JB()->common()->job()->get_logo( $job->ID, true );
$company = JB()->common()->job()->get_company_data( $job->ID );
$data['hiringOrganization'] = array();
$data['hiringOrganization']['@type'] = 'Organization';
$data['hiringOrganization']['name'] = esc_html( $company['name'] );
$company_website = $company['website'];
if ( $company_website ) {
$data['hiringOrganization']['sameAs'] = esc_url_raw( $company_website );
$data['hiringOrganization']['url'] = esc_url_raw( $company_website );
}
if ( $logo ) {
$data['hiringOrganization']['logo'] = esc_url_raw( $logo );
}
$data['identifier'] = array();
$data['identifier']['@type'] = 'PropertyValue';
$data['identifier']['name'] = esc_html( $company['name'] );
$data['identifier']['value'] = get_the_guid( $job );
$location = JB()->common()->job()->get_location( $job->ID, true );
if ( ! empty( $location ) ) {
$data['jobLocation'] = array();
$data['jobLocation']['@type'] = 'Place';
$data['jobLocation']['address'] = $this->get_structured_location( $job );
if ( empty( $data['jobLocation']['address'] ) ) {
$data['jobLocation']['address'] = esc_html( $location );
}
}
if ( JB()->options()->get( 'job-salary' ) ) {
$salary_type = get_post_meta( $job->ID, 'jb-salary-type', true );
if ( '' !== $salary_type ) {
$salary_amount_type = get_post_meta( $job->ID, 'jb-salary-amount-type', true );
$currency = JB()->options()->get( 'job-salary-currency' );
$data['baseSalary'] = array();
$data['baseSalary']['@type'] = 'MonetaryAmount';
$data['baseSalary']['currency'] = $currency;
$data['baseSalary']['value']['@type'] = 'QuantitativeValue';
if ( 'numeric' === $salary_amount_type ) {
$salary_amount = get_post_meta( $job->ID, 'jb-salary-amount', true );
if ( ! empty( $salary_amount ) ) {
$data['baseSalary']['value']['value'] = number_format( $salary_amount, 2, '.', '' );
}
} else {
$salary_min_amount = get_post_meta( $job->ID, 'jb-salary-min-amount', true );
$salary_max_amount = get_post_meta( $job->ID, 'jb-salary-max-amount', true );
if ( '' !== $salary_min_amount ) {
$data['baseSalary']['value']['maxValue'] = number_format( $salary_max_amount, 2, '.', '' );
}
if ( '' !== $salary_max_amount ) {
$data['baseSalary']['value']['maxValue'] = number_format( $salary_max_amount, 2, '.', '' );
}
}
if ( 'recurring' === $salary_type ) {
$salary_period = get_post_meta( $job->ID, 'jb-salary-period', true );
if ( ! empty( $salary_period ) ) {
$data['baseSalary']['value']['unitText'] = strtoupper( $salary_period );
}
}
}
}
/**
* Filters the job structured data.
*
* @since 1.1.0
* @hook jb-job-structured-data
*
* @param {array} $data Job's structured data.
* @param {WP_Post} $job Job post object.
*
* @return {array} Job data in raw format.
*/
return apply_filters( 'jb-job-structured-data', $data, $job ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
}
/**
* Gets the job location data.
*
* @see https://schema.org/PostalAddress
*
* @param WP_Post $job
* @return array|bool
*
* @since 1.0
*/
public function get_structured_location( $job ) {
$address = array(
'@type' => 'PostalAddress',
);
$mapping = array(
'addressLocality' => 'city',
'addressRegion' => 'state-short',
'addressCountry' => 'country-short',
);
foreach ( $mapping as $schema_key => $meta_key ) {
$value = get_post_meta( $job->ID, 'jb-location-' . $meta_key, true );
if ( ! empty( $value ) ) {
$address[ $schema_key ] = esc_html( $value );
}
}
// No address parts were found.
if ( 1 === count( $address ) ) {
$address = false;
}
/**
* Filters the job location structured data.
*
* @since 1.0
* @hook jb-job-location-structured-data
*
* @param {array} $address Job location structured data.
* @param {WP_Post} $job Job post object.
*
* @return {array} Job data in raw format.
*/
return apply_filters( 'jb-job-location-structured-data', $address, $job ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
}
/**
* Get Templates
*
* @return array
*
* @since 1.0
*/
public function get_templates() {
// phpcs:disable WordPress.PHP.NoSilencedErrors.Discouraged
$prefix = 'Job';
$dir = JB()->theme_templates;
global $wp_filesystem;
if ( ! $wp_filesystem instanceof WP_Filesystem_Base ) {
require_once ABSPATH . 'wp-admin/includes/file.php';
$credentials = request_filesystem_credentials( site_url() );
WP_Filesystem( $credentials );
}
$templates = array();
if ( $wp_filesystem->is_dir( $dir ) ) {
$handle = @opendir( $dir );
// phpcs:ignore Generic.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition -- reading folder's content here
while ( false !== ( $filename = readdir( $handle ) ) ) {
if ( '.' === $filename || '..' === $filename ) {
continue;
}
// show only root *.php files inside templates dir for getting Job templates
if ( is_dir( wp_normalize_path( $dir . DIRECTORY_SEPARATOR . $filename ) ) ) {
continue;
}
$clean_filename = $this->get_template_name( $filename );
$source = $wp_filesystem->get_contents( wp_normalize_path( $dir . DIRECTORY_SEPARATOR . $filename ) );
$tokens = @\token_get_all( $source );
$comment = array(
T_COMMENT, // All comments since PHP5
T_DOC_COMMENT, // PHPDoc comments
);
foreach ( $tokens as $token ) {
if ( in_array( $token[0], $comment, true ) && false !== strpos( $token[1], '/* ' . $prefix . ' Template:' ) ) {
$txt = $token[1];
$txt = str_replace( array( '/* ' . $prefix . ' Template: ', ' */' ), '', $txt );
$templates[ $clean_filename ] = $txt;
}
}
}
closedir( $handle );
asort( $templates );
}
return $templates;
// phpcs:enable WordPress.PHP.NoSilencedErrors.Discouraged
}
/**
* Get File Name without path and extension
*
* @param string $file
*
* @return string
*
* @since 1.0
*/
public function get_template_name( $file ) {
$file = basename( $file );
return preg_replace( '/\\.[^.\\s]{3,4}$/', '', $file );
}
/**
* Maintenance task to expire jobs.
*
* @since 1.0
*/
public function check_for_expired_jobs() {
// Change status to expire.
$job_ids = get_posts(
array(
'post_type' => 'jb-job',
'post_status' => 'publish',
'fields' => 'ids',
'posts_per_page' => -1,
'meta_query' => array(
'relation' => 'AND',
array(
'key' => 'jb-expiry-date',
'value' => gmdate( 'Y-m-d' ),
'compare' => '<=',
),
array(
'key' => 'jb-expiry-date',
'value' => '',
'compare' => '!=',
),
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',
),
),
),
)
);
if ( $job_ids ) {
foreach ( $job_ids as $job_id ) {
$job_data = array();
$job_data['ID'] = $job_id;
$job_data['post_status'] = 'jb-expired';
wp_update_post( $job_data );
/**
* Fires after Job has been expired.
*
* @since 1.1.0
* @hook jb_job_is_expired
*
* @param {int} $job_id Job ID.
*/
do_action( 'jb_job_is_expired', $job_id );
}
}
// Delete old expired jobs.
/**
* Set whether we should delete expired jobs after a certain amount of time.
*
* @since 1.0
* @hook jb_cron_delete_expired_jobs
*
* @param {bool} $address Whether we should delete expired jobs after a certain amount of time. Defaults to false.
*
* @return {bool} Delete expired jobs if set to true.
*/
if ( apply_filters( 'jb_cron_delete_expired_jobs', false ) ) {
/**
* Filters days to preserve expired job listings before deleting them.
*
* @since 1.0
* @hook jb_cron_delete_expired_jobs_days
*
* @param {int} $delete_expired_jobs_days Number of days to preserve expired job posts before deleting them. Defaults to 30 days.
*
* @return {int} Number of days to preserve expired job.
*/
$delete_expired_jobs_days = apply_filters( 'jb_cron_delete_expired_jobs_days', 30 );
$job_ids = get_posts(
array(
'post_type' => 'jb-job',
'post_status' => 'jb-expired',
'fields' => 'ids',
'date_query' => array(
array(
'column' => 'post_modified',
'before' => gmdate( 'Y-m-d', strtotime( '-' . $delete_expired_jobs_days . ' days' ) ),
),
),
'posts_per_page' => -1,
)
);
if ( $job_ids ) {
foreach ( $job_ids as $job_id ) {
wp_trash_post( $job_id );
}
}
}
}
/**
* Maintenance task to send expiration reminders jobs.
*
* @since 1.0
*/
public function check_for_reminder_expired_jobs() {
$duration = JB()->options()->get( 'job-duration' );
$reminder = JB()->options()->get( 'job-expiration-reminder' );
$days = absint( JB()->options()->get( 'job-expiration-reminder-time' ) );
if ( ! empty( $duration ) && ! empty( $reminder ) && ! empty( $days ) && $days < $duration ) {
$time = gmdate( 'Y-m-d', strtotime( '+' . $days . ' days' ) );
$args = array(
'post_type' => 'jb-job',
'post_status' => 'publish',
'meta_query' => array(
'relation' => 'AND',
array(
'key' => 'jb-is-expiration-reminded',
'compare' => 'NOT EXISTS',
),
array(
'key' => 'jb-expiry-date',
'value' => $time,
'compare' => '<=',
),
array(
'key' => 'jb-expiry-date',
'value' => '',
'compare' => '!=',
),
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',
),
),
),
'fields' => 'ids',
'posts_per_page' => - 1,
);
$job_ids = get_posts( $args );
/**
* Filters the Job IDs for reminder about expired jobs.
*
* @since 1.1.0
* @hook jb_check_for_reminder_expired_jobs_job_ids
*
* @param {array} $job_ids Job IDs.
* @param {array} $args \WP_Query arguments.
*
* @return {array} Filtered Job IDs.
*/
$job_ids = apply_filters( 'jb_check_for_reminder_expired_jobs_job_ids', $job_ids, $args );
$job_ids = array_unique( $job_ids );
if ( ! empty( $job_ids ) && ! is_wp_error( $job_ids ) ) {
$wp_timezone = wp_timezone();
foreach ( $job_ids as $job_id ) {
// when debug then endless email about expiration for checking the email content and subject
$debug = ( defined( 'JB_CRON_DEBUG' ) && JB_CRON_DEBUG );
if ( ! $debug ) {
update_post_meta( $job_id, 'jb-is-expiration-reminded', true );
}
$author_id = get_post_field( 'post_author', $job_id );
if ( empty( $author_id ) ) {
continue;
}
$time = $this->get_expiry_date_raw( $job_id );
if ( empty( $time ) || '0000-00-00' === $time ) {
continue;
}
$datetime = date_create_immutable_from_format( 'Y-m-d', $time, $wp_timezone );
if ( false === $datetime ) {
continue;
}
$origin = current_datetime();
$target = $datetime->setTimezone( $wp_timezone );
$interval = $origin->diff( $target );
$user = get_userdata( $author_id );
JB()->common()->mail()->send(
$user->user_email,
'job_expiration_reminder',
array(
'job_id' => $job_id,
'job_title' => get_the_title( $job_id ),
'job_author' => $user->display_name,
'job_expiration_days' => $interval->format( '%a' ),
'view_job_url' => get_permalink( $job_id ),
)
);
}
}
}
}
/**
* Deletes old previewed jobs to keep the DB clean.
*
* @since 1.0
*/
public function delete_old_previews() {
// Delete old jobs stuck in preview.
$job_ids = get_posts(
array(
'post_type' => 'jb-job',
'post_status' => 'jb-preview',
'fields' => 'ids',
'date_query' => array(
array(
'column' => 'post_modified',
'before' => gmdate( 'Y-m-d', strtotime( '-30 days' ) ),
),
),
'posts_per_page' => -1,
)
);
if ( ! empty( $job_ids ) && ! is_wp_error( $job_ids ) ) {
foreach ( $job_ids as $job_id ) {
wp_delete_post( $job_id, true );
}
}
}
/**
* Make location data secured after the response from GoogleMaps API
*
* @param $data
*
* @return array|mixed|object
*/
public function sanitize_location_data( $data ) {
return $this->map_deep( $data, array( $this, 'sanitize_location_data_cb' ) );
}
/**
* See the function's reference documented in wp-includes/formatting.php -> map_deep()
* Passed $key for getting different sanitizing in the callback
*
* @param $value
* @param $callback
* @param null|string $key
*
* @return array|mixed|object
*/
public function map_deep( $value, $callback, $key = null ) {
$temp_value = array();
if ( is_array( $value ) ) {
foreach ( $value as $index => $item ) {
$index_sanitized = is_string( $index ) ? sanitize_key( $index ) : $index;
$temp_value[ $index_sanitized ] = $this->map_deep( $item, $callback, $index_sanitized );
}
$value = $temp_value;
} elseif ( is_object( $value ) ) {
$temp_value = (object) $temp_value;
$object_vars = get_object_vars( $value );
foreach ( $object_vars as $property_name => $property_value ) {
$property_name_sanitized = is_string( $property_name ) ? sanitize_key( $property_name ) : $property_name;
$temp_value->$property_name_sanitized = $this->map_deep( $property_value, $callback, $property_name_sanitized );
}
$value = $temp_value;
} else {
$value = call_user_func( $callback, $value, $key );
}
return $value;
}
/**
* Sanitize Location Data response
*
* @param mixed $value
* @param null|string $key
*
* @return float|int|string
*/
public function sanitize_location_data_cb( $value, $key = null ) {
if ( is_numeric( $value ) ) {
if ( is_int( $value ) ) {
$value = (int) $value;
} elseif ( is_float( $value ) ) {
$value = (float) $value;
}
} elseif ( is_string( $value ) ) {
if ( isset( $key ) && 'adr_address' === $key ) {
$value = wp_kses_post( $value );
} else {
$value = sanitize_text_field( $value );
}
}
return $value;
}
/**
* Validate URL string
*
* @param string $url
*
* @return bool
*
* @since 1.1.0
*/
public function validate_url( $url ) {
$regex = '((https?)\:\/\/)?';
$regex .= '([a-z0-9-.]*)\.([a-z]{2,3})';
$regex .= '(\/([a-z0-9+\$_-]\.?)+)*\/?';
$regex .= '(\?[a-z+&\$_.-][a-z0-9;:@&%=+\/\$_.-]*)?';
if ( preg_match( "/^$regex$/i", $url ) ) {
return true;
}
return false;
}
/**
* Recursive function for building categories tree
*
* @param array $terms
* @param array $children Terms hierarchy
* @param int $parent_id
* @param int $level
*
* @return array
*/
public function prepare_categories_options( $terms, $children, $parent_id = 0, $level = 0 ) {
$structured_terms = array();
foreach ( $terms as $key => $term ) {
if ( (int) $term->parent !== $parent_id ) {
continue;
}
$term->level = $level;
$structured_terms[] = array( $term );
unset( $terms[ $key ] );
if ( isset( $children[ $term->term_id ] ) ) {
$structured_terms[] = $this->prepare_categories_options( array_values( $terms ), $children, $term->term_id, $level + 1 );
}
}
$structured_terms = array_merge( ...$structured_terms );
return array_values( $structured_terms );
}
/**
* @param WP_Post|array $job
*
* @return bool
*/
public function approve_job( $job ) {
if ( 'pending' !== $job->post_status ) {
return false;
}
$job_id = $job->ID;
$args = array(
'ID' => $job_id,
'post_status' => 'publish',
);
// a fix for restored from trash pending jobs
if ( 0 === strpos( $job->post_name, '__trashed' ) ) {
$args['post_name'] = sanitize_title( $job->post_title );
}
wp_update_post( $args );
delete_post_meta( $job_id, 'jb-had-pending' );
$job = get_post( $job_id );
$user = get_userdata( $job->post_author );
if ( ! empty( $user ) && ! is_wp_error( $user ) ) {
$email_args = array(
'job_id' => $job_id,
'job_title' => $job->post_title,
'job_author' => $user->display_name,
'view_job_url' => get_permalink( $job ),
);
JB()->common()->mail()->send( $user->user_email, 'job_approved', $email_args );
}
/**
* Fires after Job has been approved.
*
* @since 1.1.0
* @hook jb_job_is_approved
*
* @param {int} $post_id Post ID.
* @param {WP_Post} $post The post object.
*
* @example <caption>Updates job post meta after approving.</caption>
* function my_custom_jb_job_is_approved( $job_id, $job ) {
* update_post_meta( $job_id, 'set_some_meta_key_after_approve', true );
* }
* add_action( 'jb_job_is_approved', 'my_custom_jb_job_is_approved', 10, 2 );
*/
do_action( 'jb_job_is_approved', $job_id, $job );
return true;
}
}
}