prefix . 'postfee_entitlements'; } public static function grant( $data ) { global $wpdb; $data = wp_parse_args( $data, array( 'status' => 'active', 'created_at' => postfee_now(), ) ); $wpdb->insert( self::table(), $data ); return (int) $wpdb->insert_id; } public static function get( $id ) { global $wpdb; return $wpdb->get_row( $wpdb->prepare( 'SELECT * FROM %i WHERE id = %d', self::table(), $id ) ); } public static function update( $id, $data ) { global $wpdb; return $wpdb->update( self::table(), $data, array( 'id' => $id ) ); } /** * Entitlements a user can still post with, soonest to expire first. */ public static function usable_for_user( $user_id ) { global $wpdb; return $wpdb->get_results( $wpdb->prepare( "SELECT * FROM %i WHERE user_id = %d AND status IN ('active','cancelling') AND (expires_at IS NULL OR expires_at > %s) AND (credits_total IS NULL OR credits_used < credits_total) ORDER BY expires_at IS NULL, expires_at ASC, id ASC", self::table(), $user_id, postfee_now() ) ); } /** * Running subscriptions and unexpired packages, including used-up ones. */ public static function current_for_user( $user_id ) { global $wpdb; return $wpdb->get_results( $wpdb->prepare( "SELECT * FROM %i WHERE user_id = %d AND status IN ('active','cancelling') AND (expires_at IS NULL OR expires_at > %s) ORDER BY id DESC", self::table(), $user_id, postfee_now() ) ); } public static function find_usable( $user_id, $post_type ) { foreach ( self::usable_for_user( $user_id ) as $entitlement ) { if ( Plans::allows_post_type( $entitlement->plan_id, $post_type ) ) { return $entitlement; } } return null; } public static function find_by_sub( $gateway, $sub_id ) { global $wpdb; return $wpdb->get_row( $wpdb->prepare( 'SELECT * FROM %i WHERE gateway = %s AND gateway_sub_id = %s ORDER BY id DESC LIMIT 1', self::table(), $gateway, $sub_id ) ); } public static function find_by_order( $order_id ) { global $wpdb; return $wpdb->get_results( $wpdb->prepare( 'SELECT * FROM %i WHERE order_id = %d', self::table(), $order_id ) ); } /** * Credits left, or null when unlimited. */ public static function remaining( $entitlement ) { return null === $entitlement->credits_total ? null : max( 0, (int) $entitlement->credits_total - (int) $entitlement->credits_used ); } /** * Spends one credit. The WHERE clause re-checks availability so two * simultaneous submissions cannot both spend the last credit. */ public static function consume( $id ) { global $wpdb; return 1 === $wpdb->query( $wpdb->prepare( "UPDATE %i SET credits_used = credits_used + 1 WHERE id = %d AND status IN ('active','cancelling') AND (expires_at IS NULL OR expires_at > %s) AND (credits_total IS NULL OR credits_used < credits_total)", self::table(), $id, postfee_now() ) ); } /** * Gives one spent credit back. */ public static function release( $id ) { global $wpdb; return 1 === $wpdb->query( $wpdb->prepare( 'UPDATE %i SET credits_used = credits_used - 1 WHERE id = %d AND credits_used > 0', self::table(), $id ) ); } /** * Starts a new subscription period: credits reset and access extended. */ public static function renew( $id, $period_end ) { return self::update( $id, array( 'credits_used' => 0, 'expires_at' => gmdate( 'Y-m-d H:i:s', $period_end + DAY_IN_SECONDS ), ) ); } /** * @return object[] The entitlements that just expired. */ public static function expire_due() { global $wpdb; $due = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM %i WHERE status IN ('active','cancelling') AND expires_at IS NOT NULL AND expires_at <= %s", self::table(), postfee_now() ) ); foreach ( $due as $entitlement ) { self::update( $entitlement->id, array( 'status' => 'expired' ) ); } return $due; } }