Source: includes/admin/class-metabox.php

<?php
namespace jb\admin;

use WP_Post;
use WP_Term;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

if ( ! class_exists( 'jb\admin\Metabox' ) ) {

	/**
	 * Class Metabox
	 *
	 * @package jb\admin
	 */
	class Metabox {

		/**
		 * @var array
		 *
		 * @since 1.0
		 */
		public $nonce = array();

		/**
		 * Metabox constructor.
		 */
		public function __construct() {
			add_action( 'load-post.php', array( &$this, 'add_metabox' ), 9 );
			add_action( 'load-post-new.php', array( &$this, 'add_metabox' ), 9 );

			add_action( 'jb-job-type_add_form_fields', array( &$this, 'job_type_create' ) );
			add_action( 'jb-job-type_edit_form_fields', array( &$this, 'job_type_edit' ) );
			add_action( 'create_jb-job-type', array( &$this, 'save_job_type_meta' ) );
			add_action( 'edited_jb-job-type', array( &$this, 'save_job_type_meta' ) );
		}

		/**
		 * Add custom fields on Job Type Create form
		 *
		 * @since 1.0
		 */
		public function job_type_create() {
			include_once JB()->admin()->templates_path . 'job-type' . DIRECTORY_SEPARATOR . 'styling-create.php';

			wp_nonce_field( basename( __FILE__ ), 'jb_job_type_styling_nonce' );
		}

		/**
		 * Add custom fields on Job Type Edit form
		 *
		 * @param WP_Term $term
		 *
		 * @since 1.0
		 */
		public function job_type_edit( $term ) {
			$term_id = $term->term_id;

			$data                  = array();
			$data['jb-color']      = get_term_meta( $term_id, 'jb-color', true );
			$data['jb-background'] = get_term_meta( $term_id, 'jb-background', true );

			include_once JB()->admin()->templates_path . 'job-type' . DIRECTORY_SEPARATOR . 'styling-edit.php';

			wp_nonce_field( basename( __FILE__ ), 'jb_job_type_styling_nonce' );
		}

		/**
		 * Save custom data for Job Type
		 *
		 * @param int $term_id
		 *
		 * @since 1.0
		 */
		public function save_job_type_meta( $term_id ) {
			// validate nonce
			if ( ! isset( $_REQUEST['jb_job_type_styling_nonce'] ) || ! wp_verify_nonce( sanitize_key( $_REQUEST['jb_job_type_styling_nonce'] ), basename( __FILE__ ) ) ) {
				return;
			}

			// validate user
			$term     = get_term( $term_id );
			$taxonomy = get_taxonomy( $term->taxonomy );

			if ( ! current_user_can( $taxonomy->cap->edit_terms, $term_id ) ) {
				return;
			}

			if ( ! empty( $_REQUEST['jb-color'] ) ) {
				update_term_meta( $term_id, 'jb-color', sanitize_hex_color( wp_unslash( $_REQUEST['jb-color'] ) ) );
			} else {
				delete_term_meta( $term_id, 'jb-color' );
			}

			if ( ! empty( $_REQUEST['jb-background'] ) ) {
				update_term_meta( $term_id, 'jb-background', sanitize_hex_color( wp_unslash( $_REQUEST['jb-background'] ) ) );
			} else {
				delete_term_meta( $term_id, 'jb-background' );
			}
		}

		/**
		 * Checking CPT screen
		 *
		 * @since 1.0
		 */
		public function add_metabox() {
			global $current_screen;

			if ( 'jb-job' === $current_screen->id && current_user_can( 'edit_jb-jobs' ) ) {
				add_action( 'add_meta_boxes', array( &$this, 'add_metabox_job' ) );
				add_action( 'save_post', array( &$this, 'save_metabox_job' ), 10, 2 );
			}
		}

		/**
		 * Load a form metabox
		 *
		 * @param object $job Not used.
		 * @param array $box
		 *
		 * @since 1.0
		 */
		public function load_metabox_job( /** @noinspection PhpUnusedParameterInspection */$job, $box ) {
			$metabox = str_replace( 'jb-job-', '', $box['id'] );

			include_once JB()->admin()->templates_path . 'job' . DIRECTORY_SEPARATOR . $metabox . '.php';

			if ( empty( $this->nonce['job'] ) ) {
				$this->nonce['job'] = true;
				wp_nonce_field( basename( __FILE__ ), 'jb_job_save_metabox_nonce' );
			}
		}

		/**
		 * Add form metabox
		 *
		 * @since 1.0
		 */
		public function add_metabox_job() {
			add_meta_box( 'jb-job-data', __( 'Job Data', 'jobboardwp' ), array( &$this, 'load_metabox_job' ), 'jb-job', 'normal', 'core' );
		}

		/**
		 * Save job metabox
		 *
		 * @param int $post_id
		 * @param WP_Post $post
		 *
		 * @since 1.0
		 */
		public function save_metabox_job( $post_id, $post ) {
			// validate nonce
			if ( ! isset( $_POST['jb_job_save_metabox_nonce'] ) || ! wp_verify_nonce( sanitize_key( $_POST['jb_job_save_metabox_nonce'] ), basename( __FILE__ ) ) ) {
				return;
			}

			// validate post type
			if ( 'jb-job' !== $post->post_type ) {
				return;
			}

			// validate post type object
			$post_type = get_post_type_object( $post->post_type );
			if ( null === $post_type ) {
				return;
			}

			// validate user
			if ( ! current_user_can( $post_type->cap->edit_post, $post_id ) ) {
				return;
			}

			// location validation
			/**
			 * Filters the checking job location type.
			 *
			 * @since 1.2.2
			 * @hook jb_location_type_disable
			 *
			 * @param {bool} $disable Checking job location type. Set to true if disable job location type.
			 *
			 * @return {bool} Login form visibility.
			 */
			$location_type_disable = apply_filters( 'jb_location_type_disable', false );
			if ( false === (bool) $location_type_disable ) {
				if ( ! isset( $_POST['jb-job-meta']['jb-location-type'] ) ) {
					return;
				}
				if ( empty( $_POST['jb-job-meta']['jb-location'] ) && sanitize_text_field( wp_unslash( $_POST['jb-job-meta']['jb-location-type'] ) ) === '0' ) {
					return;
				}
			}

			$sanitize_map = array(
				'jb-author'              => 'absint',
				'jb-application-contact' => 'text',
				'jb-job-type'            => 'absint',
				'jb-job-category'        => 'absint',
				'jb-location-type'       => 'text',
				'jb-location'            => 'text',
				'jb-location-preferred'  => 'text',
				'jb-company-name'        => 'text',
				'jb-company-website'     => 'text',
				'jb-company-tagline'     => 'text',
				'jb-company-twitter'     => 'text',
				'jb-company-facebook'    => 'text',
				'jb-company-instagram'   => 'text',
				'jb-is-filled'           => 'bool',
				'jb-expiry-date'         => 'text',
				'jb-is-featured'         => 'bool',
				'jb-featured-order'      => 'absint',
				'jb-salary-type'         => 'text',
				'jb-salary-amount-type'  => 'text',
				'jb-salary-amount'       => 'absint',
				'jb-salary-min-amount'   => 'absint',
				'jb-salary-max-amount'   => 'absint',
				'jb-salary-period'       => 'text',
			);

			$current_time = time();

			// merge preferred location into location
			if ( isset( $_POST['jb-job-meta']['jb-location-type'] ) && '0' !== sanitize_text_field( wp_unslash( $_POST['jb-job-meta']['jb-location-type'] ) ) ) {
				if ( isset( $_POST['jb-job-meta']['jb-location-preferred'] ) ) {
					$_POST['jb-job-meta']['jb-location'] = wp_unslash( $_POST['jb-job-meta']['jb-location-preferred'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- sanitized below
					unset( $_POST['jb-job-meta']['jb-location-preferred'] );
				}
				if ( isset( $_POST['jb-job-meta']['jb-location-preferred-data'] ) ) {
					$_POST['jb-job-meta']['jb-location-data'] = wp_unslash( $_POST['jb-job-meta']['jb-location-preferred-data'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- sanitized below
					unset( $_POST['jb-job-meta']['jb-location-preferred-data'] );
				}
			}

			if ( empty( $_POST['jb-job-meta']['jb-is-featured'] ) ) {
				unset( $_POST['jb-job-meta']['jb-featured-order'] );
			} elseif ( empty( $_POST['jb-job-meta']['jb-featured-order'] ) ) {
				// workaround if user set featured option but doesn't the order
				$_POST['jb-job-meta']['jb-featured-order'] = 1;
			}

			$skip_meta_update = array();

			//save metadata
			foreach ( $_POST['jb-job-meta'] as $k => $v ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash -- sanitized below
				if ( 0 === strpos( $k, 'jb-' ) ) {
					if ( in_array( $k, $skip_meta_update, true ) ) {
						continue;
					}

					$meta_key = sanitize_key( $k );

					if ( isset( $sanitize_map[ $meta_key ] ) ) {
						switch ( $sanitize_map[ $meta_key ] ) {
							case 'bool':
								$v = (bool) $v;
								break;
							case 'text':
								$v = sanitize_text_field( wp_unslash( $v ) );
								break;
							case 'absint':
								if ( is_array( $v ) ) {
									$v = array_map( 'absint', $v );
								} else {
									$v = absint( $v );
								}
								break;
						}
					}

					if ( 'jb-job-type' === $meta_key ) {
						if ( ! is_array( $v ) ) {
							$v = ! empty( $v ) ? array( $v ) : '';
						}
						wp_set_post_terms( $post_id, $v, 'jb-job-type' );
						continue;
					}

					if ( JB()->options()->get( 'job-categories' ) ) {
						if ( 'jb-job-category' === $meta_key ) {
							if ( ! is_array( $v ) ) {
								$v = ! empty( $v ) ? array( $v ) : '';
							}
							wp_set_post_terms( $post_id, $v, 'jb-job-category' );
							continue;
						}
					}

					if ( 'jb-author' === $meta_key ) {
						global $wpdb;
						$wpdb->update( $wpdb->posts, array( 'post_author' => $v ), array( 'ID' => $post_id ), array( '%d' ), array( '%d' ) );
						continue;
					}

					if ( 'jb-is-filled' === $meta_key ) {
						if ( ! empty( $v ) ) {
							if ( ! JB()->common()->job()->is_filled( $post_id ) ) {
								/** This action is documented in includes/ajax/class-jobs.php */
								do_action( 'jb_fill_job', $post_id, $post );
							}
						} elseif ( JB()->common()->job()->is_filled( $post_id ) ) {
							/** This action is documented in includes/ajax/class-jobs.php */
							do_action( 'jb_unfill_job', $post_id, $post );
						}
					}

					if ( 'jb-expiry-date' === $meta_key ) {
						if ( empty( $v ) ) {
							if ( JB()->options()->get( 'individual-job-duration' ) ) {
								/**
								 * Filters the default individual job expiration date.
								 *
								 * Note: It works only in case if the expiration date has been empty in the posting form.
								 *
								 * @since 1.1.0
								 * @hook jb_default_individual_expiry
								 *
								 * @param {string} $expiration_date Default Job expiration date. It's '' by default and job is unexpired.
								 *
								 * @return {string} Job expiration date.
								 */
								$v = apply_filters( 'jb_default_individual_expiry', '' );
							} else {
								$v = JB()->common()->job()->calculate_expiry();
							}
						} else {
							$date = strtotime( $v, $current_time );
							$v    = gmdate( 'Y-m-d', $date );
							if ( $current_time >= $date ) {
								global $wpdb;
								$wpdb->update( $wpdb->posts, array( 'post_status' => 'jb-expired' ), array( 'ID' => $post_id ), array( '%s' ), array( '%d' ) );
								/** This action is documented in includes/common/class-job.php */
								do_action( 'jb_job_is_expired', $post_id );
							}
						}
					}

					if ( 'jb-location-data' === $k ) {
						$v = json_decode( wp_unslash( $v ) );
						$v = JB()->common()->job()->sanitize_location_data( $v );

						update_post_meta( $post_id, 'jb-location-raw-data', $v );

						if ( isset( $v->geometry, $v->geometry->location ) ) {
							if ( isset( $v->geometry->location->lat ) ) {
								update_post_meta( $post_id, 'jb-location-lat', sanitize_text_field( $v->geometry->location->lat ) );
							}
							if ( isset( $v->geometry->location->lng ) ) {
								update_post_meta( $post_id, 'jb-location-long', sanitize_text_field( $v->geometry->location->lng ) );
							}
						}
						if ( isset( $v->formatted_address ) ) {
							update_post_meta( $post_id, 'jb-location-formatted-address', sanitize_text_field( $v->formatted_address ) );
						}

						if ( ! empty( $v->address_components ) ) {
							$address_data = $v->address_components;

							foreach ( $address_data as $data ) {
								switch ( $data->types[0] ) {
									case 'sublocality_level_1':
									case 'locality':
									case 'postal_town':
										update_post_meta( $post_id, 'jb-location-city', sanitize_text_field( $data->long_name ) );
										break;
									case 'administrative_area_level_1':
									case 'administrative_area_level_2':
										update_post_meta( $post_id, 'jb-location-state-short', sanitize_text_field( $data->short_name ) );
										update_post_meta( $post_id, 'jb-location-state-long', sanitize_text_field( $data->long_name ) );
										break;
									case 'country':
										update_post_meta( $post_id, 'jb-location-country-short', sanitize_text_field( $data->short_name ) );
										update_post_meta( $post_id, 'jb-location-country-long', sanitize_text_field( $data->long_name ) );
										break;
								}
							}
						}

						continue;
					}

					// Flush salary data if it isn't supported by the current salary type.
					if ( 'jb-salary-type' === $k ) {
						if ( empty( $v ) ) {
							$skip_meta_update = array_merge(
								$skip_meta_update,
								array(
									'jb-salary-type',
									'jb-salary-amount-type',
									'jb-salary-amount',
									'jb-salary-min-amount',
									'jb-salary-max-amount',
									'jb-salary-period',
								)
							);
							delete_post_meta( $post_id, 'jb-salary-type' );
							delete_post_meta( $post_id, 'jb-salary-amount-type' );
							delete_post_meta( $post_id, 'jb-salary-amount' );
							delete_post_meta( $post_id, 'jb-salary-min-amount' );
							delete_post_meta( $post_id, 'jb-salary-max-amount' );
							delete_post_meta( $post_id, 'jb-salary-period' );
							continue;
						} elseif ( 'fixed' === $v ) {
							$skip_meta_update = array_merge(
								$skip_meta_update,
								array(
									'jb-salary-period',
								)
							);
							delete_post_meta( $post_id, 'jb-salary-period' );
						}
					}

					update_post_meta( $post_id, $k, $v );

					// Flush salary data if it isn't supported by the current salary type.
					if ( 'jb-salary-amount-type' === $k ) {
						if ( 'numeric' === $v ) {
							$skip_meta_update = array_merge(
								$skip_meta_update,
								array(
									'jb-salary-min-amount',
									'jb-salary-max-amount',
								)
							);

							delete_post_meta( $post_id, 'jb-salary-min-amount' );
							delete_post_meta( $post_id, 'jb-salary-max-amount' );
						} elseif ( 'range' === $v ) {
							$skip_meta_update = array_merge(
								$skip_meta_update,
								array(
									'jb-salary-amount',
								)
							);
							delete_post_meta( $post_id, 'jb-salary-amount' );
						}
					}

					// Flush featured order in case when the job isn't featured.
					if ( 'jb-is-featured' === $k && empty( $v ) ) {
						delete_post_meta( $post_id, 'jb-featured-order' );
					}
				}
			}

			update_post_meta( $post_id, 'jb-last-edit-date', $current_time );

			/**
			 * Fires after job submission and pass validation through wp-admin.
			 *
			 * @since 1.2.3
			 * @hook jb_job_after_save_metabox
			 *
			 * @param {string} $post_id Job's ID.
			 * @param {object} $post    Post object.
			 */
			do_action( 'jb_job_after_save_metabox', $post_id, $post );
		}
	}
}