Skip to content

Instantly share code, notes, and snippets.

@actual-saurabh
Last active April 7, 2022 09:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save actual-saurabh/a479571b6ceb5e6b32e30f31575050f0 to your computer and use it in GitHub Desktop.
Save actual-saurabh/a479571b6ceb5e6b32e30f31575050f0 to your computer and use it in GitHub Desktop.
Serial Numbers for Transaction Receipts
<?php // Do not copy this line
// copy from below this line
/**
* Generates sequential serial numbers for a given post type
*/
class LLMS_Serial_Number_Generator {
private $post_type = '';
private $post_status = '';
private $prefix = '';
private $length = 7;
public function __construct() {
$this->hook();
}
/**
* Hooks into Purchase Receipt html
*/
public function hook() {
add_filter( 'llms_notification_viewpurchase_receipt_get_email_html', array( $this, 'replace_merge_code' ), 10, 2 );
}
/**
* Initialises serial number generation.
*
* @param string $post_type Post Type to generate for
* @param string $post_status Generate when transitioning to this post status
* @param string $length Desired length of the serial number including leading zeroes but excluding prefix
* @param string $prefix Desired prefix
*
* @return bool|null
*/
public function init ( $length = false, $prefix = false ) {
$this->post_type = 'llms_transaction';
$this->post_status = array( 'llms-txn-succeeded', 'llms-txn-refunded' );
$this->length = ! empty( $length ) ? $length : $this->length;
$this->prefix = ! empty( $prefix ) ? $prefix : $this->prefix;
}
/*
* Replace serial number merge code
*
* @param string $email_html The email html
* @param LLMS_Notification_View_Purchase_Receipt $notification_view The view handler of purchase receipt notification
*
* @return string
*/
public function replace_merge_code( $email_html, $notification_view ){
// get the notification.
$notification = new LLMS_Notification( $notification_view->id );
// get the transaction post from the notification.
$transaction = get_post( $notification->get( 'post_id' ) );
// generate serial number.
$serial_number = $this->maybe_generate( $transaction );
// don't replace merge code on error.
if( is_wp_error( $serial_number ) ){
return $email_html;
}
// replace merge code
$email_html = str_replace( '{{SERIAL_NUMBER}}', $serial_number, $email_html );
return $email_html;
}
/**
* Generates and saves serial number for post.
*
* @param string $old_status Earlier post status
* @param string $new_status New post status
* @param WP_Post $post The current post
*/
public function maybe_generate( $transaction ) {
// initiliase.
$this->init();
// first check if the serial number is already in the meta.
$existing_serial_number = get_post_meta( $transaction->ID, '_llms_serial_number', true );
// if it's there, save further execution and bail.
if( ! empty( $existing_serial_number ) ) {
return $existing_serial_number;
}
// no serial number in meta, will need to generate.
$serial_number = $this->generate( $transaction );
return $serial_number;
}
/**
* Generates a serial number for support.
*
* @param WP_Post $post The post to generate serial number for
*
* @return string
*/
public function generate( $transaction ) {
$is_test = ( 'live' !== get_post_meta( $transaction->ID, '_llms_api_mode', true ) ) ? true : false;
$last_serial_number = $this->get_last_serial_number( $transaction, $is_test );
// either this is the first post of its kind (last serial number is 0), or we have a definite existing last serial number.
$current_serial_number = $this->increment_serial_number( $last_serial_number, $is_test );
if ( ! $is_test ) {
// save the serial number in meta, only for live transactions.
update_post_meta( $transaction->ID, '_llms_serial_number', $current_serial_number );
}
return $current_serial_number;
}
/**
* Gets the last serial number value.
*
* @param WP_Post $post The post to generate serial number for
*
* @return string
*/
public function get_last_serial_number( $transaction, $is_test ) {
$last_serial_number = 0;
// get previous post.
$previous_transaction = $this->get_previous_transaction( $transaction );
// there is a previous transaction, attempt getting the previous serial number.
if( ! empty ( $previous_transaction ) ) {
$last_serial_number = get_post_meta( $previous_transaction->ID, '_llms_serial_number', true );
}
// there is a previous transaction but it doesn't have a serial number, this is probably the first run
if( empty ( $last_serial_number ) && ! $is_test ) {
$last_serial_number = $this->maybe_first_run( $previous_transaction );
}
// there is no previous transaction, serial number is 0
return $last_serial_number;
}
/**
* Increments the last serial number.
*
* @param string $last_serial_number The post to generate serial number for
*
* @return string
*/
public function increment_serial_number( &$last_serial_number ) {
// replace the prefix, if needed
if( ! empty( $this->prefix ) ) {
$last_serial_number = str_replace( $this->prefix, '', $last_serial_number );
}
$last_serial_number = intval( $last_serial_number ) + 1;
// increment and add leading zeroes.
$new_serial_number = sprintf( "%0{$this->length}d", $last_serial_number ) ;
// prefix if needed.
if( ! empty( $this->prefix ) ) {
$new_serial_number = $this->prefix . $new_serial_number;
}
return $new_serial_number;
}
/**
* Fetches the previous post
*
* @param WP_Post $post The current post
*
* @return WP_Post
*/
private function get_previous_transaction( $transaction ){
// bail, if no post is provided
if ( ! $transaction ) {
return null;
}
$post_status__in = "'" . implode( "','", $this->post_status ) . "'";
global $wpdb;
$where = $wpdb->prepare(
"WHERE p.post_date < %s AND p.post_type = %s AND p.post_status IN ( $post_status__in ) AND m.meta_key = %s AND m.meta_value = %s",
$transaction->post_date, $transaction->post_type , '_llms_api_mode', 'live'
);
$sort = "ORDER BY p.post_date DESC LIMIT 1";
$query = "SELECT p.ID FROM $wpdb->posts AS p LEFT JOIN $wpdb->postmeta as m ON p.ID = m.post_id $where $sort";
$previous_transaction = $wpdb->get_var( $query );
if ( null === $previous_transaction ) {
$previous_transaction = '';
}
if ( $previous_transaction ) {
$previous_transaction = get_post( $previous_transaction );
}
return $previous_transaction;
}
/**
* Creates all older serial numbers on first run.
*
* @param WP_Post $previous_post The previous post.
*
* @return WP_Post
*/
public function maybe_first_run( $previous_transaction ) {
$older_transactions = $this->get_older();
if( ! $older_transactions ) {
return 0;
}
return $this->first_run( $older_transactions, $previous_transaction );
}
public function first_run( $older_transactions, $previous_transaction ) {
$older_serial_numbers_generated = $this->generate_all_at_once( $older_transactions );
// return the error if there was one.
if( is_wp_error( $older_serial_numbers_generated ) ) {
return $older_serial_numbers_generated;
}
/*
* bypass cache for last serial number by directly querying the database
* because it was just added and won't show up in get_post_meta untill a reload.
*/
if( empty ( $previous_transaction ) ) {
return 0;
}
global $wpdb;
$last_serial_number = $wpdb->get_var(
$wpdb->prepare(
"SELECT meta_value FROM $wpdb->postmeta WHERE meta_key=%s AND post_id=%d ",
'_llms_serial_number', $previous_transaction->ID
)
);
return $last_serial_number;
}
public function get_older() {
// set up query args.
$older_transactions_query_args = array(
'post_type' => $this->post_type,
'post_status' => $this->post_status,
'posts_per_page' => -1, // we want all the posts at once
'fields' => 'ids', // we just need the IDs
'order' => 'ASC',
'orderby' => 'date',
'meta_query' => array(
array(
'key' => '_llms_api_mode',
'value' => 'live',
),
),
);
// get query results.
$older_transactions = new WP_Query( $older_transactions_query_args );
// no posts found. bail!
if( empty( $older_transactions->found_posts ) ) {
return false;
}
return $older_transactions;
}
/**
* Retroactively generates and saves serial numbers for all post
*
* @return bool|WP_Error
*/
private function generate_all_at_once( $older_transactions ) {
// get the post IDs.
$older_transaction_ids = $older_transactions->posts;
// generate as many serial numbers as needed.
$serial_numbers = range( 1, intval( $older_transactions->found_posts ) );
$meta_key = '_llms_serial_number';
global $wpdb;
// initialise db query
$sql_query = "INSERT INTO $wpdb->postmeta ( post_id, meta_key, meta_value ) ";
$counter = 0;
foreach( $older_transaction_ids as $post_id ) {
// use up one serial number from left in each iteration
$meta_value = $this->increment_serial_number( $counter );
// set up part of the database query
$sql_query_sel[]= "SELECT $post_id, '$meta_key', '$meta_value'";
}
// setup complete
$sql_query.= implode( " UNION ALL ", $sql_query_sel );
$results = $wpdb->query( $sql_query );
if( ! $results ){
return new WP_Error( 'failed', __( 'Failed saving serial numbers for older transactions.', 'lifterlms' ) );
}
// this would be the last, ie, current serial number
return true;
}
}
new LLMS_Serial_Number_Generator();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment