Skip to content
3 changes: 0 additions & 3 deletions .psalm/psalm-baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -237,9 +237,6 @@
<code>false</code>
<code>string</code>
</InvalidNullableReturnType>
<NullArgument>
<code>null</code>
</NullArgument>
<NullableReturnStatement>
<code>null</code>
</NullableReturnStatement>
Expand Down
10 changes: 10 additions & 0 deletions includes/admin/class-wp-job-manager-settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
4 changes: 2 additions & 2 deletions includes/class-wp-job-manager-post-types.php
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down
53 changes: 51 additions & 2 deletions includes/forms/class-wp-job-manager-form-submit-job.php
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,17 @@ 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',
];
}

return $this->fields;
}

Expand Down Expand Up @@ -573,6 +584,26 @@ 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() );
$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 ) {
$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' ) );
}
}
}

/**
* Perform additional validation on the job submission fields.
*
Expand Down Expand Up @@ -1018,6 +1049,21 @@ 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 ) {

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.
} else {
update_post_meta( $this->job_id, '_' . $key, $values[ $group_key ][ $key ] );
Expand Down Expand Up @@ -1163,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 = [];
Expand Down
Loading