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; } }