Skip to content

馃悶 Bug: Renewing an expired listing from admin does not restore Published status#2964

Description

@RabbiIslamRony

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

  1. Open the expired listing in WordPress admin.
  2. Set its expiration date to a valid future date.
  3. Update the listing.
  4. 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

  1. Open the same expired listing in WordPress admin.
  2. Enable Never Expires.
  3. Update the listing.
  4. 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:

  1. A scalar status cannot be both expired and private, so the renewal branch cannot run for a current expired listing.
  2. 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

  • I have reproduced this bug in localhost.
  • This bug happens with a default WordPress theme active.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions