Enable Expiry Date in Frontend Form#2895
Enable Expiry Date in Frontend Form#2895masteradhoc wants to merge 7 commits intoAutomattic:trunkfrom
Conversation
|
@donnchawp could you check this PR as well? |
donnchawp
left a comment
There was a problem hiding this comment.
Thanks for your PR. Some changes here.
we'll keep the lower one, works.
donnchawp
left a comment
There was a problem hiding this comment.
I used 3 AIs to review this PR and they found issues. I was able to reproduce the "preview->submit" deletion of the expiry date too.
Multi-AI adversarial review (Claude + Gemini + Codex). Three of these findings have multi-reviewer consensus; two HIGH-severity findings were each surfaced solo and then independently confirmed via direct code reading.
Summary
- 6 distinct findings — 1 consensus (3/3), 2 partial-consensus (2/3), 2 solo HIGH, 1 LOW
- Severity mix: 2 HIGH, 2 MEDIUM, 2 LOW
- Verdict: BLOCK — the feature is broken end-to-end on the standard new-submission flow
The single most important issue: a user's chosen expiry date is silently discarded between preview and publish, so the feature never actually works for new submissions.
Findings
[HIGH] includes/forms/class-wp-job-manager-form-submit-job.php:1158 — User-chosen expiry is wiped between preview and publish
Status: Solo (Codex, confirmed by Claude via direct code read)
update_job_data() saves the user's date via set_job_expiration() while the post is in preview status. Then preview_handler() runs on "Continue" and unconditionally calls delete_post_meta( $job->ID, '_job_expires' )
(line 1158) before transitioning the post to publish/pending. The set_expiry() hook on the transition reads $_POST['_job_expires'], which is never set by this form (it submits job[job_expires]), so it falls into
the ! $expiration_date branch and writes the default duration. Net result: the user's date never survives a normal submission.
Fix: In preview_handler, only delete _job_expires when the new field is disabled or empty; or copy the saved value into $_POST['_job_expires'] before wp_update_post() so set_expiry() honors it (mirrors how the
admin write panel feeds the same key).
[HIGH] includes/forms/class-wp-job-manager-form-submit-job.php:586 — Past-date validator traps owners of expired listings
Status: Solo (Gemini, confirmed by Claude via direct code read)
WP_Job_Manager_Form_Edit_Job::populate_fields() (line 126) pre-fills every field's value from get_post_meta($id, '_' . $key, true) — i.e., the existing _job_expires. For an expired listing this is a past date. When the
owner edits any unrelated field and saves, $_POST['job']['job_expires'] carries the unchanged past date, and validate_fields() throws "Expiry date cannot be in the past". The user is locked out of editing expired listings
until they also pick a new future date — even for typo fixes.
Fix: Skip the past-date check when the submitted value is unchanged from the stored meta, or when the listing is already expired.
[MEDIUM] includes/forms/class-wp-job-manager-form-submit-job.php:1029-1037 — Clearing the field on edit doesn't reset to default
Status: Consensus (Claude + Codex + Gemini, 3/3)
The new elseif ( 'job_expires' === $key ) branch only writes when the value is non-empty. On the inherited frontend edit form, clearing the date submits empty → no write → existing _job_expires persists. The field's own
description says "Leave blank to use default duration." It doesn't.
Fix: When empty( $values[$group_key][$key] ), call set_job_expiration( $this->job_id, null ) so the default is recomputed on next transition.
[MEDIUM] includes/forms/class-wp-job-manager-form-submit-job.php:579-588 — Date-component overflow not validated
Status: Consensus (Claude + Codex)
Codex confirmed via REPL: DateTimeImmutable::createFromFormat('Y-m-d|', '2026-02-31', wp_timezone()) returns 2026-03-03 (warning_count=1, error_count=0). The ! $expires_date check passes, and an impossible date silently
becomes a different valid date. The HTML5/datepicker UI normally prevents this, but a JS-disabled client or direct POST bypasses both. Server validation should not depend on the UI.
Fix: After createFromFormat, also reject if DateTimeImmutable::getLastErrors()['warning_count'] > 0, or compare $expires_date->format('Y-m-d') === $values['job']['job_expires'].
[LOW] includes/forms/class-wp-job-manager-form-submit-job.php:399-401 — attributes['min'] and class keys are dead code
Status: Consensus (Claude + Gemini)
templates/form-fields/date-field.php does not iterate $field['attributes'] and hardcodes the class — so min => current_time('Y-m-d') and class => 'job-manager-datepicker' are silently dropped. type=>'date' reads as
native HTML5 to a casual reviewer, but the template actually renders <input type="text"> plus a jQuery UI datepicker. Min-date enforcement comes from datepicker.js using browser-local "today", not server time.
Fix: Remove the attributes and class keys from the field definition (or extend the template to honor them).
[LOW] Notes
type => 'date'is misleading; ensure the feature description doesn't imply native HTML5 date-picker UX.- No automated test coverage was added. Given two HIGH bugs slipped through, an integration test for
submit-with-date → preview → publish → assert _job_expiresis strongly recommended.
Highest-priority single action
Fix the preview→publish regression first — without it, the feature ships broken regardless of every other issue.
Fixes #2886
Changes Proposed in this Pull Request
Testing Instructions
Release Notes
New or Updated Hooks and Templates
Deprecated Code
Screenshot / Video