Directorist version
8.9.3
Environment
- WordPress 7.0.4
- PHP 8.2.29
- LocalWP
- Reproduced with Directorist Core
- Reproduced through the actual
ATBDP_Metabox::save_post_meta() admin save path
Summary
Renewing an expired listing from the WordPress admin saves the new expiration data but does not restore the listing to Published status.
This affects both supported admin renewal inputs:
- A future expiration date
- The Never Expires option
The listing remains in the expired post status, so it stays unavailable to public listing queries even though its expiration metadata is now valid.
User impact
- An administrator sees the updated expiration value but the listing remains under Expired.
- The direct/public listing may remain unavailable.
- The listing does not return to normal archive/grid queries.
- Re-saving the expiration setting does not repair the post status.
- Administrators must separately republish every affected listing.
This can repeatedly affect any listing renewed from the admin until the status transition is corrected.
Reproduction setup
Create a listing with this initial state:
| Property |
Value |
| Post type |
at_biz_dir |
| Post status |
expired |
| Expiration date |
Any past MySQL datetime |
| Never Expires |
Disabled |
Steps to reproduce: future expiration
- Open the expired listing in WordPress admin.
- Set its expiration date to a valid future date.
- Update the listing.
- Read
_expiry_date and the WordPress post status.
Actual result
| Property |
Result |
_expiry_date |
Updated to the future value |
| Post status |
Still expired |
Steps to reproduce: Never Expires
- Open the same expired listing in WordPress admin.
- Enable Never Expires.
- Update the listing.
- Read
_never_expire and the WordPress post status.
Actual result
| Property |
Result |
_never_expire |
Saved |
| Post status |
Still expired |
Expected result
When an administrator gives an expired listing a valid future expiration date or enables Never Expires, the listing should transition from expired to publish during the same save operation.
A past/invalid expiration date must not republish the listing.
Root cause
The relevant code is in includes/classes/class-metabox.php, inside ATBDP_Metabox::save_post_meta().
The current condition is:
$listing_status = get_post_status( $post_id );
if ( empty( $listing_status ) || ( 'expired' === $listing_status ) && ( 'private' === $listing_status ) ) {
if ( ( $expire_date > $current_date ) || $should_never_expire ) {
wp_update_post(
[
'ID' => $post_id,
'post_status' => $listing_status,
'meta_input' => [
'_listing_status' => 'post_status',
],
]
);
}
}
There are two independent problems:
- A scalar status cannot be both
expired and private, so the renewal branch cannot run for a current expired listing.
- Even if the branch ran,
post_status is assigned the existing status instead of publish.
Regression context
Commit a1b75daa8a3ea824f75af784a71a85212aca9013 originally fixed admin renewal by checking two different sources:
- Legacy
_listing_status === expired
- WordPress
post_status === private
That implementation published the post after a valid renewal.
After listing-status migration, the logic now reads the WordPress post status into one variable, but the old two-source expired && private condition was retained against that single value. The migrated condition is therefore unsatisfiable.
Scope
This report is specifically for renewal through the WordPress admin metabox.
Frontend/dashboard renewal has a separate code path in ATBDP_Add_Listing::renew_listing(), which already changes the post status to publish.
Cron also has a recovery query for expired listings with a future expiration/Never Expires state, but admin renewal should not depend on a later cron run to complete the status transition.
Suggested implementation direction
Determine the current and legacy expired states separately:
$post_status = get_post_status( $post_id );
$is_current_expired = 'expired' === $post_status;
$is_legacy_expired = (
'private' === $post_status
&& 'expired' === get_post_meta( $post_id, '_listing_status', true )
);
$has_valid_renewal = $should_never_expire || $expire_date > current_time( 'mysql' );
When ( $is_current_expired || $is_legacy_expired ) && $has_valid_renewal is true:
- Set
post_status to publish.
- Normalize or remove legacy status metadata according to the existing status-migration contract.
- Reset any renewal-reminder state if required.
- Ensure the second
save_post pass caused by wp_update_post() exits because the status is now Published, or add an explicit re-entry guard.
The exact legacy-meta cleanup should follow the existing migration helpers rather than introducing a new status value.
Compatibility considerations
- Do not publish listings renewed with a past expiration date.
- Never Expires should take precedence over the calculated expiration date.
- Preserve Pricing Plans behavior when a paid renewal requires checkout; this issue concerns the direct admin save path.
- Confirm whether admin renewal should fire
atbdp_after_renewal. Avoid changing notification/order behavior unintentionally.
- Preserve administrator capability and nonce checks already performed by the metabox save handler.
- Avoid infinite
save_post recursion.
Test matrix
| Initial status |
New expiration |
Never Expires |
Expected status |
expired |
Future |
No |
publish |
expired |
Past |
No |
expired |
expired |
Empty/default finite |
No |
Based on calculated expiration; never publish if not future |
expired |
Any |
Yes |
publish |
publish |
Future |
No |
publish |
publish |
Any |
Yes |
publish |
Legacy private + legacy expired meta |
Future |
No |
publish |
draft |
Future |
No |
draft |
pending |
Future |
No |
pending |
Also verify:
- Expiration metadata is saved once.
- The status transition does not loop recursively.
- The renewed listing is returned by normal public listing queries immediately.
- Existing frontend/dashboard renewal remains unchanged.
Acceptance criteria
- Future-date admin renewal republishes an expired listing during the same request.
- Never Expires admin renewal republishes an expired listing during the same request.
- Invalid/past dates do not publish an expired listing.
- Draft and pending moderation states are not bypassed.
- Legacy expired records are handled safely.
- No save recursion, duplicate notifications, or order side effects are introduced.
- The behavior is covered by a regression test.
Isolating the problem
Directorist version
8.9.3
Environment
ATBDP_Metabox::save_post_meta()admin save pathSummary
Renewing an expired listing from the WordPress admin saves the new expiration data but does not restore the listing to Published status.
This affects both supported admin renewal inputs:
The listing remains in the
expiredpost status, so it stays unavailable to public listing queries even though its expiration metadata is now valid.User impact
This can repeatedly affect any listing renewed from the admin until the status transition is corrected.
Reproduction setup
Create a listing with this initial state:
at_biz_direxpiredSteps to reproduce: future expiration
_expiry_dateand the WordPress post status.Actual result
_expiry_dateexpiredSteps to reproduce: Never Expires
_never_expireand the WordPress post status.Actual result
_never_expireexpiredExpected result
When an administrator gives an expired listing a valid future expiration date or enables Never Expires, the listing should transition from
expiredtopublishduring the same save operation.A past/invalid expiration date must not republish the listing.
Root cause
The relevant code is in
includes/classes/class-metabox.php, insideATBDP_Metabox::save_post_meta().The current condition is:
There are two independent problems:
expiredandprivate, so the renewal branch cannot run for a current expired listing.post_statusis assigned the existing status instead ofpublish.Regression context
Commit
a1b75daa8a3ea824f75af784a71a85212aca9013originally fixed admin renewal by checking two different sources:_listing_status === expiredpost_status === privateThat implementation published the post after a valid renewal.
After listing-status migration, the logic now reads the WordPress post status into one variable, but the old two-source
expired && privatecondition was retained against that single value. The migrated condition is therefore unsatisfiable.Scope
This report is specifically for renewal through the WordPress admin metabox.
Frontend/dashboard renewal has a separate code path in
ATBDP_Add_Listing::renew_listing(), which already changes the post status topublish.Cron also has a recovery query for expired listings with a future expiration/Never Expires state, but admin renewal should not depend on a later cron run to complete the status transition.
Suggested implementation direction
Determine the current and legacy expired states separately:
When
( $is_current_expired || $is_legacy_expired ) && $has_valid_renewalis true:post_statustopublish.save_postpass caused bywp_update_post()exits because the status is now Published, or add an explicit re-entry guard.The exact legacy-meta cleanup should follow the existing migration helpers rather than introducing a new status value.
Compatibility considerations
atbdp_after_renewal. Avoid changing notification/order behavior unintentionally.save_postrecursion.Test matrix
expiredpublishexpiredexpiredexpiredexpiredpublishpublishpublishpublishpublishprivate+ legacy expired metapublishdraftdraftpendingpendingAlso verify:
Acceptance criteria
Isolating the problem