false,
'protected' => true,
'exclude_from_search' => true,
'show_in_admin_all_list' => true,
'show_in_admin_status_list' => true,
);
register_post_status(
self::UNPAID,
$args + array(
'label' => __( 'Awaiting payment', 'postfee' ),
/* translators: %s: number of posts */
'label_count' => _n_noop( 'Awaiting payment (%s)', 'Awaiting payment (%s)', 'postfee' ),
)
);
register_post_status(
self::EXPIRED,
$args + array(
'label' => __( 'Expired', 'postfee' ),
/* translators: %s: number of posts */
'label_count' => _n_noop( 'Expired (%s)', 'Expired (%s)', 'postfee' ),
)
);
}
public static function status_label( $status ) {
$labels = array(
'publish' => __( 'Live', 'postfee' ),
'pending' => __( 'Awaiting review', 'postfee' ),
'draft' => __( 'Draft', 'postfee' ),
self::UNPAID => __( 'Awaiting payment', 'postfee' ),
self::EXPIRED => __( 'Expired', 'postfee' ),
);
return isset( $labels[ $status ] ) ? $labels[ $status ] : $status;
}
/**
* Puts a paid-for post live (or into review), using the terms of the
* entitlement that paid for it.
*/
public static function activate( $post_id, $entitlement ) {
// The listing and featured clocks start when the post is actually
// published, which may be later if it has to be reviewed first.
update_post_meta( $post_id, '_postfee_pending_listing_days', (int) $entitlement->listing_days );
update_post_meta( $post_id, '_postfee_pending_featured_days', (int) $entitlement->featured_days );
update_post_meta( $post_id, '_postfee_plan_id', (int) $entitlement->plan_id );
update_post_meta( $post_id, '_postfee_entitlement_id', (int) $entitlement->id );
update_post_meta( $post_id, '_postfee_paid_at', time() );
// Kept on the post so it stays as it was when paid for, like the days above.
$plan = Plans::get( $entitlement->plan_id );
update_post_meta( $post_id, '_postfee_return_window', $plan ? $plan['return_window'] : 'forever' );
delete_post_meta( $post_id, '_postfee_live_at' );
delete_post_meta( $post_id, '_postfee_suspended' );
$status = 'pending' === postfee_setting( 'paid_status' ) ? 'pending' : 'publish';
$status = apply_filters( 'postfee_paid_post_status', $status, $post_id, $entitlement );
wp_update_post(
array(
'ID' => $post_id,
'post_status' => $status,
'post_date' => current_time( 'mysql' ),
'post_date_gmt' => current_time( 'mysql', 1 ),
)
);
do_action( 'postfee_post_activated', $post_id, $entitlement );
}
/**
* Sends a post back to "awaiting payment", e.g. after a refund.
*/
public static function deactivate( $post_id ) {
self::unfeature( $post_id );
delete_post_meta( $post_id, '_postfee_expires' );
delete_post_meta( $post_id, '_postfee_entitlement_id' );
wp_update_post(
array(
'ID' => $post_id,
'post_status' => self::UNPAID,
)
);
}
/**
* Takes down the live posts of a subscription that ended, when its plan
* says so. They are marked so resume() can tell them from posts whose
* listing time ran out.
*/
public static function suspend( $entitlement ) {
$plan = Plans::get( $entitlement->plan_id );
$post_types = postfee_post_types();
if ( ! $plan || 'subscription' !== $plan['type'] || ! $plan['unpublish_on_end'] || ! $post_types ) {
return;
}
$post_ids = get_posts(
array(
'post_type' => $post_types,
'post_status' => 'publish',
'posts_per_page' => -1,
'fields' => 'ids',
'meta_key' => '_postfee_entitlement_id', // phpcs:ignore WordPress.DB.SlowDBQuery -- Runs from cron or a payment, not on page views.
'meta_value' => (int) $entitlement->id, // phpcs:ignore WordPress.DB.SlowDBQuery -- Runs from cron or a payment, not on page views.
)
);
foreach ( $post_ids as $post_id ) {
update_post_meta( $post_id, '_postfee_suspended', (int) $entitlement->id );
wp_update_post(
array(
'ID' => $post_id,
'post_status' => self::EXPIRED,
)
);
do_action( 'postfee_post_suspended', $post_id, $entitlement );
}
}
/**
* Puts back the posts suspend() took down, when the subscription is
* renewed or the user subscribes to the same plan again.
*/
public static function resume( $entitlement ) {
$post_types = postfee_post_types();
if ( ! $post_types ) {
return;
}
$post_ids = get_posts(
array(
'post_type' => $post_types,
'post_status' => self::EXPIRED,
'author' => (int) $entitlement->user_id,
'posts_per_page' => -1,
'fields' => 'ids',
'meta_query' => array( // phpcs:ignore WordPress.DB.SlowDBQuery -- Runs from cron or a payment, not on page views.
array( 'key' => '_postfee_suspended', 'compare' => 'EXISTS' ),
array( 'key' => '_postfee_plan_id', 'value' => (int) $entitlement->plan_id ),
),
)
);
foreach ( $post_ids as $post_id ) {
$expires = (int) get_post_meta( $post_id, '_postfee_expires', true );
if ( $expires && $expires <= time() ) {
// Its listing time ran out in the meantime: now simply expired.
delete_post_meta( $post_id, '_postfee_suspended' );
continue;
}
update_post_meta( $post_id, '_postfee_entitlement_id', (int) $entitlement->id );
wp_update_post(
array(
'ID' => $post_id,
'post_status' => 'publish',
)
);
// Removed last: on_transition() uses it to keep the listing time.
delete_post_meta( $post_id, '_postfee_suspended' );
do_action( 'postfee_post_resumed', $post_id, $entitlement );
}
}
public static function apply_upgrade( $post_id, $plan ) {
$post = get_post( $post_id );
if ( ! $post ) {
return false;
}
$listing = (int) $plan['listing_days'];
$featured = (int) $plan['featured_days'];
if ( 'publish' === $post->post_status ) {
$expires = (int) get_post_meta( $post_id, '_postfee_expires', true );
// A post without an expiry is already listed forever.
if ( $listing && $expires ) {
update_post_meta( $post_id, '_postfee_expires', max( time(), $expires ) + $listing * DAY_IN_SECONDS );
}
if ( $featured ) {
$until = (int) get_post_meta( $post_id, '_postfee_featured_until', true );
self::feature( $post_id, max( time(), $until ) + $featured * DAY_IN_SECONDS );
}
} else {
// Not live yet: bank the days until the post is published.
foreach ( array( 'listing' => $listing, 'featured' => $featured ) as $key => $days ) {
if ( $days ) {
update_post_meta( $post_id, "_postfee_pending_{$key}_days", (int) get_post_meta( $post_id, "_postfee_pending_{$key}_days", true ) + $days );
}
}
// A post taken down with its subscription only comes back with it.
if ( self::EXPIRED === $post->post_status && $listing && ! get_post_meta( $post_id, '_postfee_suspended', true ) ) {
wp_update_post(
array(
'ID' => $post_id,
'post_status' => 'publish',
)
);
}
}
do_action( 'postfee_post_upgraded', $post_id, $plan );
return true;
}
public static function on_transition( $new_status, $old_status, $post ) {
if ( 'publish' !== $new_status || 'publish' === $old_status ) {
return;
}
$listing = get_post_meta( $post->ID, '_postfee_pending_listing_days', true );
$featured = get_post_meta( $post->ID, '_postfee_pending_featured_days', true );
if ( '' !== $listing ) {
// Relisting through an upgrade does not restart the clock.
if ( ! get_post_meta( $post->ID, '_postfee_live_at', true ) ) {
update_post_meta( $post->ID, '_postfee_live_at', time() );
}
delete_post_meta( $post->ID, '_postfee_pending_listing_days' );
if ( $listing > 0 ) {
update_post_meta( $post->ID, '_postfee_expires', time() + $listing * DAY_IN_SECONDS );
} else {
delete_post_meta( $post->ID, '_postfee_expires' );
}
} elseif ( self::EXPIRED === $old_status && ! get_post_meta( $post->ID, '_postfee_suspended', true ) ) {
// Republished by hand in wp-admin: don't let cron expire it again.
delete_post_meta( $post->ID, '_postfee_expires' );
}
if ( '' !== $featured ) {
delete_post_meta( $post->ID, '_postfee_pending_featured_days' );
if ( $featured > 0 ) {
self::feature( $post->ID, time() + $featured * DAY_IN_SECONDS );
}
}
}
/**
* A post that is trashed or deleted while live or in review gives the
* post it used back to its author, for as long as its plan allows.
* Expired posts have had their run.
*/
public static function on_delete( $post_id ) {
$post = get_post( $post_id );
if ( ! $post || ! in_array( $post->post_type, postfee_post_types(), true ) ) {
return;
}
self::unfeature( $post_id );
if ( in_array( $post->post_status, array( 'publish', 'pending' ), true ) ) {
Fulfillment::return_credit( $post_id );
}
}
/**
* WordPress restores trashed posts as drafts. One that still has to be
* paid for goes back to where the author can find and pay for it.
*/
public static function untrash_status( $new_status, $post_id, $previous_status ) {
if ( ! in_array( get_post_type( $post_id ), postfee_post_types(), true ) ) {
return $new_status;
}
if ( in_array( $previous_status, array( self::UNPAID, self::EXPIRED ), true ) ) {
return $previous_status;
}
// Paid for once, and the post it used was given back when it was trashed.
if ( get_post_meta( $post_id, '_postfee_plan_id', true ) && ! get_post_meta( $post_id, '_postfee_entitlement_id', true ) ) {
return self::UNPAID;
}
return $new_status;
}
public static function feature( $post_id, $until ) {
update_post_meta( $post_id, '_postfee_featured_until', (int) $until );
if ( 'post' === get_post_type( $post_id ) ) {
stick_post( $post_id );
}
}
public static function unfeature( $post_id ) {
if ( ! get_post_meta( $post_id, '_postfee_featured_until', true ) ) {
return;
}
delete_post_meta( $post_id, '_postfee_featured_until' );
if ( 'post' === get_post_type( $post_id ) ) {
unstick_post( $post_id );
}
}
public static function is_featured( $post_id ) {
return (int) get_post_meta( $post_id, '_postfee_featured_until', true ) > time();
}
private static function due( $meta_key ) {
$post_types = postfee_post_types();
if ( ! $post_types ) {
return array();
}
return get_posts(
array(
'post_type' => $post_types,
'post_status' => 'publish',
'posts_per_page' => 200,
'fields' => 'ids',
'no_found_rows' => true,
'meta_query' => array( // phpcs:ignore WordPress.DB.SlowDBQuery -- Runs from cron or a payment, not on page views.
array(
'key' => $meta_key,
'value' => time(),
'compare' => '<=',
'type' => 'NUMERIC',
),
),
)
);
}
public static function cron() {
foreach ( self::due( '_postfee_featured_until' ) as $post_id ) {
self::unfeature( $post_id );
}
foreach ( self::due( '_postfee_expires' ) as $post_id ) {
self::unfeature( $post_id );
wp_update_post(
array(
'ID' => $post_id,
'post_status' => self::EXPIRED,
)
);
do_action( 'postfee_post_expired', $post_id );
}
foreach ( Entitlements::expire_due() as $entitlement ) {
self::suspend( $entitlement );
}
}
public static function post_class( $classes, $css_class, $post_id ) {
if ( self::is_featured( $post_id ) ) {
$classes[] = 'postfee-featured';
}
return $classes;
}
public static function post_states( $states, $post ) {
if ( in_array( $post->post_status, array( self::UNPAID, self::EXPIRED ), true ) ) {
$states[ $post->post_status ] = self::status_label( $post->post_status );
}
if ( self::is_featured( $post->ID ) ) {
$states['postfee_featured'] = __( 'Featured', 'postfee' );
}
return $states;
}
}