From 0142f49a9e3f4024a7984d8019ee43711f158767 Mon Sep 17 00:00:00 2001 From: Brian Date: Sun, 10 Aug 2025 10:47:40 +0200 Subject: [PATCH 1/9] add new settings option to enable expiry date --- includes/admin/class-wp-job-manager-settings.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/includes/admin/class-wp-job-manager-settings.php b/includes/admin/class-wp-job-manager-settings.php index 9a38516f4..f2316f4cc 100644 --- a/includes/admin/class-wp-job-manager-settings.php +++ b/includes/admin/class-wp-job-manager-settings.php @@ -389,6 +389,16 @@ protected function init_settings() { 'attributes' => [], 'track' => 'bool', ], + [ + 'name' => 'job_manager_enable_expiry_date_field', + 'std' => '0', + 'label' => __( 'Expiry Date Field', 'wp-job-manager' ), + 'cb_label' => __( 'Enable expiry date field in frontend form', 'wp-job-manager' ), + 'desc' => __( 'Allow employers to set a custom expiry date for their job listings that overrides the default listing duration.', 'wp-job-manager' ), + 'type' => 'checkbox', + 'attributes' => [], + 'track' => 'bool', + ], [ 'name' => 'job_manager_generate_username_from_email', 'std' => '1', From ecff64b154f66d71db48dee093e66ab8ed5e0b71 Mon Sep 17 00:00:00 2001 From: Brian Date: Sun, 10 Aug 2025 10:48:51 +0200 Subject: [PATCH 2/9] show field if settings allow it, validation, etc. --- .../class-wp-job-manager-form-submit-job.php | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/includes/forms/class-wp-job-manager-form-submit-job.php b/includes/forms/class-wp-job-manager-form-submit-job.php index 281cdd91a..5ac1c5494 100644 --- a/includes/forms/class-wp-job-manager-form-submit-job.php +++ b/includes/forms/class-wp-job-manager-form-submit-job.php @@ -389,6 +389,21 @@ public function init_fields() { ]; } + if ( get_option( 'job_manager_enable_expiry_date_field' ) ) { + $this->fields['job']['job_expires'] = [ + 'label' => __( 'Expiry Date', 'wp-job-manager' ), + 'description' => __( 'Optionally set when this job listing will expire. Leave blank to use default duration.', 'wp-job-manager' ), + 'type' => 'date', + 'required' => false, + 'placeholder' => '', + 'priority' => '6.6', + 'class' => 'job-manager-datepicker', + 'attributes' => [ + 'min' => current_time( 'Y-m-d' ) + ], + ]; + } + return $this->fields; } @@ -524,6 +539,19 @@ protected function validate_fields( $values ) { } } + // Validate expiry date field. + if ( get_option( 'job_manager_enable_expiry_date_field' ) && ! empty( $values['job']['job_expires'] ) ) { + $expires_date = DateTimeImmutable::createFromFormat( 'Y-m-d', $values['job']['job_expires'], wp_timezone() ); + if ( ! $expires_date ) { + return new WP_Error( 'validation-error', __( 'Please enter a valid expiry date.', 'wp-job-manager' ) ); + } + + $today = new DateTimeImmutable( 'now', wp_timezone() ); + if ( $expires_date < $today ) { + return new WP_Error( 'validation-error', __( 'Expiry date cannot be in the past.', 'wp-job-manager' ) ); + } + } + // Application method. if ( ! $this->should_application_field_skip_email_url_validation() && isset( $values['job']['application'] ) ) { $allowed_application_method = get_option( 'job_manager_allowed_application_method', '' ); @@ -561,6 +589,20 @@ protected function validate_fields( $values ) { } } + // Validate expiry date field if enabled and provided + if ( get_option( 'job_manager_enable_expiry_date_field' ) && ! empty( $values['job']['job_expires'] ) ) { + $expires_date = DateTimeImmutable::createFromFormat( 'Y-m-d', $values['job']['job_expires'], wp_timezone() ); + if ( ! $expires_date ) { + throw new Exception( __( 'Please enter a valid expiry date', 'wp-job-manager' ) ); + } + + // Check if the date is in the past + $today = new DateTimeImmutable( 'today', wp_timezone() ); + if ( $expires_date < $today ) { + throw new Exception( __( 'Expiry date cannot be in the past', 'wp-job-manager' ) ); + } + } + /** * Perform additional validation on the job submission fields. * @@ -997,6 +1039,19 @@ protected function update_job_data( $values ) { } update_user_meta( get_current_user_id(), '_company_logo', $attachment_id ); + // Handle custom expiry date field. + } elseif ( 'job_expires' === $key ) { + // Always save the raw value first + update_post_meta( $this->job_id, '_job_expires', $values[ $group_key ][ $key ] ); + + // If a date is provided, also set the job expiration + if ( ! empty( $values[ $group_key ][ $key ] ) ) { + $expires_date = DateTimeImmutable::createFromFormat( 'Y-m-d', $values[ $group_key ][ $key ], wp_timezone() ); + if ( $expires_date ) { + WP_Job_Manager_Post_Types::instance()->set_job_expiration( $this->job_id, $expires_date ); + } + } + // Save meta data. } else { update_post_meta( $this->job_id, '_' . $key, $values[ $group_key ][ $key ] ); From c24fdea9fd90abe0a39c7f268a672eca31c7caa7 Mon Sep 17 00:00:00 2001 From: Brian Date: Mon, 16 Mar 2026 11:09:54 +0100 Subject: [PATCH 3/9] fix php lint --- .../class-wp-job-manager-form-submit-job.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/includes/forms/class-wp-job-manager-form-submit-job.php b/includes/forms/class-wp-job-manager-form-submit-job.php index 5ac1c5494..86e96ff07 100644 --- a/includes/forms/class-wp-job-manager-form-submit-job.php +++ b/includes/forms/class-wp-job-manager-form-submit-job.php @@ -399,7 +399,7 @@ public function init_fields() { 'priority' => '6.6', 'class' => 'job-manager-datepicker', 'attributes' => [ - 'min' => current_time( 'Y-m-d' ) + 'min' => current_time( 'Y-m-d' ), ], ]; } @@ -545,7 +545,7 @@ protected function validate_fields( $values ) { if ( ! $expires_date ) { return new WP_Error( 'validation-error', __( 'Please enter a valid expiry date.', 'wp-job-manager' ) ); } - + $today = new DateTimeImmutable( 'now', wp_timezone() ); if ( $expires_date < $today ) { return new WP_Error( 'validation-error', __( 'Expiry date cannot be in the past.', 'wp-job-manager' ) ); @@ -589,14 +589,14 @@ protected function validate_fields( $values ) { } } - // Validate expiry date field if enabled and provided + // Validate expiry date field if enabled and provided. if ( get_option( 'job_manager_enable_expiry_date_field' ) && ! empty( $values['job']['job_expires'] ) ) { $expires_date = DateTimeImmutable::createFromFormat( 'Y-m-d', $values['job']['job_expires'], wp_timezone() ); if ( ! $expires_date ) { throw new Exception( __( 'Please enter a valid expiry date', 'wp-job-manager' ) ); } - - // Check if the date is in the past + + // Check if the date is in the past. $today = new DateTimeImmutable( 'today', wp_timezone() ); if ( $expires_date < $today ) { throw new Exception( __( 'Expiry date cannot be in the past', 'wp-job-manager' ) ); @@ -1041,10 +1041,10 @@ protected function update_job_data( $values ) { // Handle custom expiry date field. } elseif ( 'job_expires' === $key ) { - // Always save the raw value first + // Always save the raw value first. update_post_meta( $this->job_id, '_job_expires', $values[ $group_key ][ $key ] ); - - // If a date is provided, also set the job expiration + + // If a date is provided, also set the job expiration. if ( ! empty( $values[ $group_key ][ $key ] ) ) { $expires_date = DateTimeImmutable::createFromFormat( 'Y-m-d', $values[ $group_key ][ $key ], wp_timezone() ); if ( $expires_date ) { From 31b36d6a427a2c9db5457da600761ddf860b619d Mon Sep 17 00:00:00 2001 From: Brian Date: Sun, 3 May 2026 10:18:43 +0200 Subject: [PATCH 4/9] ensure its a date not datetime --- includes/forms/class-wp-job-manager-form-submit-job.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/includes/forms/class-wp-job-manager-form-submit-job.php b/includes/forms/class-wp-job-manager-form-submit-job.php index 86e96ff07..a4961543d 100644 --- a/includes/forms/class-wp-job-manager-form-submit-job.php +++ b/includes/forms/class-wp-job-manager-form-submit-job.php @@ -591,7 +591,7 @@ protected function validate_fields( $values ) { // Validate expiry date field if enabled and provided. if ( get_option( 'job_manager_enable_expiry_date_field' ) && ! empty( $values['job']['job_expires'] ) ) { - $expires_date = DateTimeImmutable::createFromFormat( 'Y-m-d', $values['job']['job_expires'], wp_timezone() ); + $expires_date = DateTimeImmutable::createFromFormat( 'Y-m-d|', $values['job']['job_expires'], wp_timezone() ); if ( ! $expires_date ) { throw new Exception( __( 'Please enter a valid expiry date', 'wp-job-manager' ) ); } @@ -1046,7 +1046,7 @@ protected function update_job_data( $values ) { // If a date is provided, also set the job expiration. if ( ! empty( $values[ $group_key ][ $key ] ) ) { - $expires_date = DateTimeImmutable::createFromFormat( 'Y-m-d', $values[ $group_key ][ $key ], wp_timezone() ); + $expires_date = DateTimeImmutable::createFromFormat( 'Y-m-d|', $values[ $group_key ][ $key ], wp_timezone() ); if ( $expires_date ) { WP_Job_Manager_Post_Types::instance()->set_job_expiration( $this->job_id, $expires_date ); } From 0c21135525c11976aad851ba8330b7207a9c2df8 Mon Sep 17 00:00:00 2001 From: Brian Date: Sun, 3 May 2026 10:23:31 +0200 Subject: [PATCH 5/9] remove double validation we'll keep the lower one, works. --- .../forms/class-wp-job-manager-form-submit-job.php | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/includes/forms/class-wp-job-manager-form-submit-job.php b/includes/forms/class-wp-job-manager-form-submit-job.php index a4961543d..4c39883f4 100644 --- a/includes/forms/class-wp-job-manager-form-submit-job.php +++ b/includes/forms/class-wp-job-manager-form-submit-job.php @@ -539,19 +539,6 @@ protected function validate_fields( $values ) { } } - // Validate expiry date field. - if ( get_option( 'job_manager_enable_expiry_date_field' ) && ! empty( $values['job']['job_expires'] ) ) { - $expires_date = DateTimeImmutable::createFromFormat( 'Y-m-d', $values['job']['job_expires'], wp_timezone() ); - if ( ! $expires_date ) { - return new WP_Error( 'validation-error', __( 'Please enter a valid expiry date.', 'wp-job-manager' ) ); - } - - $today = new DateTimeImmutable( 'now', wp_timezone() ); - if ( $expires_date < $today ) { - return new WP_Error( 'validation-error', __( 'Expiry date cannot be in the past.', 'wp-job-manager' ) ); - } - } - // Application method. if ( ! $this->should_application_field_skip_email_url_validation() && isset( $values['job']['application'] ) ) { $allowed_application_method = get_option( 'job_manager_allowed_application_method', '' ); From 78e8992deac670ffc94ecb168b16bd1fecb32d85 Mon Sep 17 00:00:00 2001 From: Brian Date: Sun, 3 May 2026 10:37:02 +0200 Subject: [PATCH 6/9] set_job_expiration() handles saving _job_expires already --- includes/forms/class-wp-job-manager-form-submit-job.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/includes/forms/class-wp-job-manager-form-submit-job.php b/includes/forms/class-wp-job-manager-form-submit-job.php index 4c39883f4..932add202 100644 --- a/includes/forms/class-wp-job-manager-form-submit-job.php +++ b/includes/forms/class-wp-job-manager-form-submit-job.php @@ -1028,8 +1028,6 @@ protected function update_job_data( $values ) { // Handle custom expiry date field. } elseif ( 'job_expires' === $key ) { - // Always save the raw value first. - update_post_meta( $this->job_id, '_job_expires', $values[ $group_key ][ $key ] ); // If a date is provided, also set the job expiration. if ( ! empty( $values[ $group_key ][ $key ] ) ) { From 5862caae885159c5dc60e358e825118bd74ce4f1 Mon Sep 17 00:00:00 2001 From: Brian Date: Tue, 19 May 2026 20:44:41 +0200 Subject: [PATCH 7/9] fix findings --- .../class-wp-job-manager-form-submit-job.php | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/includes/forms/class-wp-job-manager-form-submit-job.php b/includes/forms/class-wp-job-manager-form-submit-job.php index 40251094b..5a5c4ba21 100644 --- a/includes/forms/class-wp-job-manager-form-submit-job.php +++ b/includes/forms/class-wp-job-manager-form-submit-job.php @@ -409,10 +409,6 @@ public function init_fields() { 'required' => false, 'placeholder' => '', 'priority' => '6.6', - 'class' => 'job-manager-datepicker', - 'attributes' => [ - 'min' => current_time( 'Y-m-d' ), - ], ]; } @@ -591,14 +587,20 @@ protected function validate_fields( $values ) { // Validate expiry date field if enabled and provided. if ( get_option( 'job_manager_enable_expiry_date_field' ) && ! empty( $values['job']['job_expires'] ) ) { $expires_date = DateTimeImmutable::createFromFormat( 'Y-m-d|', $values['job']['job_expires'], wp_timezone() ); - if ( ! $expires_date ) { + $date_errors = DateTimeImmutable::getLastErrors(); + if ( ! $expires_date || ( is_array( $date_errors ) && $date_errors['warning_count'] > 0 ) ) { throw new Exception( __( 'Please enter a valid expiry date', 'wp-job-manager' ) ); } // Check if the date is in the past. + // Skip the check when editing an expired listing with an unchanged date — otherwise + // owners would be locked out of any unrelated edits on their expired listings. $today = new DateTimeImmutable( 'today', wp_timezone() ); if ( $expires_date < $today ) { - throw new Exception( __( 'Expiry date cannot be in the past', 'wp-job-manager' ) ); + $existing_expires = $this->job_id ? get_post_meta( $this->job_id, '_job_expires', true ) : ''; + if ( $values['job']['job_expires'] !== $existing_expires ) { + throw new Exception( __( 'Expiry date cannot be in the past', 'wp-job-manager' ) ); + } } } @@ -1050,12 +1052,16 @@ protected function update_job_data( $values ) { // Handle custom expiry date field. } elseif ( 'job_expires' === $key ) { - // If a date is provided, also set the job expiration. if ( ! empty( $values[ $group_key ][ $key ] ) ) { + // A date was provided — persist it. $expires_date = DateTimeImmutable::createFromFormat( 'Y-m-d|', $values[ $group_key ][ $key ], wp_timezone() ); if ( $expires_date ) { WP_Job_Manager_Post_Types::instance()->set_job_expiration( $this->job_id, $expires_date ); } + } else { + // Empty: clear the stored date so the default duration is recomputed on the + // next status transition (matches field description "Leave blank to use default duration"). + WP_Job_Manager_Post_Types::instance()->set_job_expiration( $this->job_id, null ); } // Save meta data. @@ -1203,8 +1209,11 @@ public function preview_handler() { $job = get_post( $this->job_id ); if ( in_array( $job->post_status, [ 'preview', 'expired' ], true ) ) { - // Reset expiry. - delete_post_meta( $job->ID, '_job_expires' ); + // Reset expiry — but preserve a user-chosen date when the expiry date field is active. + $saved_expiry = get_post_meta( $job->ID, '_job_expires', true ); + if ( ! get_option( 'job_manager_enable_expiry_date_field' ) || empty( $saved_expiry ) ) { + delete_post_meta( $job->ID, '_job_expires' ); + } // Update job listing. $update_job = []; From e531878804615ed2ae916693b73f6525fa99b219 Mon Sep 17 00:00:00 2001 From: Brian Date: Tue, 19 May 2026 20:48:24 +0200 Subject: [PATCH 8/9] add null param documentation --- includes/class-wp-job-manager-post-types.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/includes/class-wp-job-manager-post-types.php b/includes/class-wp-job-manager-post-types.php index b28d53d84..0269f391f 100644 --- a/includes/class-wp-job-manager-post-types.php +++ b/includes/class-wp-job-manager-post-types.php @@ -1169,8 +1169,8 @@ public function set_expiry( $post ) { /** * Set the job expiration date. * - * @param WP_Post|int $job Job post object or ID. - * @param DateTimeImmutable $date_expires Date time object for the job expiration date. Null if to be cleared. + * @param WP_Post|int $job Job post object or ID. + * @param DateTimeImmutable|null $date_expires Date time object for the job expiration date. Null to clear. * * @return false * @throws TypeError When bad argument is passed to `$date_expires`. From 45a2ef9fb906a8e7bfc402a966bd6c2e0f9ddf76 Mon Sep 17 00:00:00 2001 From: Brian Date: Tue, 19 May 2026 18:59:57 +0000 Subject: [PATCH 9/9] remove nullargument from psalm-baseline --- .psalm/psalm-baseline.xml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.psalm/psalm-baseline.xml b/.psalm/psalm-baseline.xml index adc575cad..7058458f4 100644 --- a/.psalm/psalm-baseline.xml +++ b/.psalm/psalm-baseline.xml @@ -237,9 +237,6 @@ false string - - null - null