Skip to content

Instantly share code, notes, and snippets.

@mlbd
Created September 3, 2025 21:35
Show Gist options
  • Save mlbd/8d8bc2561d67f00993719eb2f3f8aa93 to your computer and use it in GitHub Desktop.
Save mlbd/8d8bc2561d67f00993719eb2f3f8aa93 to your computer and use it in GitHub Desktop.
Auto-reject after one, send an email after each assignment is completed, and add a ‘Last Signed Off’ placeholder.
add_filter('owf_review_step_total_reviews', function ($total_reviews, $review_setting, $action_history_id, $step_id) {
global $wpdb;
/**
* NEW: For steps requiring "everyone" to approve, if ANY reviewer has rejected (unable),
* auto-reject all remaining "assignment" reviews immediately so the failure branch proceeds now.
* We also persist these changes so inbox/reminders are cleaned up.
*/
if ( ! empty( $total_reviews ) ) {
if ( $review_setting === 'everyone' ) {
$action_table = OW_Utility::instance()->get_action_table_name();
$formated_total_reviews = $total_reviews;
// 1) Find the first rejector (unable) row, if any.
$reject_row = null;
foreach ( $total_reviews as $r ) {
if ( isset( $r->review_status ) && $r->review_status === 'unable' ) {
$reject_row = $r;
break;
}
}
// 2) If we have a rejector and there are still pending assignments, flip them to unable.
if ( $reject_row ) {
$rejector_user = get_userdata( intval( $reject_row->actor_id ) );
$rejector_name = $rejector_user ? $rejector_user->display_name : __( 'another reviewer', 'oasisworkflow' );
$now_time = current_time( 'mysql' );
$ow_email_local = new OW_Email();
foreach ( $total_reviews as $id => $r ) {
if ( isset( $r->review_status ) && $r->review_status === 'assignment' ) {
// Build/append auto-comment (JSON shape you requested).
$existing = array();
if ( ! empty( $r->comments ) ) {
$decoded = json_decode( $r->comments, true );
if ( is_array( $decoded ) ) {
$existing = $decoded;
}
}
$existing[] = array(
'send_id' => intval( $r->actor_id ), // as per your example
'comment' => sprintf( __( 'Auto-rejected: another reviewer (%s) rejected this step.', 'oasisworkflow' ), $rejector_name ),
'comment_timestamp' => $now_time,
);
$new_comments_json = wp_json_encode( $existing );
$formated_total_reviews[ $id ]->review_status = 'unable';
$formated_total_reviews[ $id ]->comments = $new_comments_json;
$formated_total_reviews[ $id ]->step_id = $new_comments_json;
// Clone critical fields from the rejector row to align failure path.
$new_step_id = isset( $reject_row->step_id ) ? $reject_row->step_id : $r->step_id;
$new_next_assign_json = isset( $reject_row->next_assign_actors ) ? $reject_row->next_assign_actors : $r->next_assign_actors;
$new_due_date = isset( $reject_row->due_date ) ? $reject_row->due_date : $r->due_date;
// Clone critical fields from the rejector row to align failure path.
$formated_total_reviews[ $id ]->step_id = $new_step_id;
$formated_total_reviews[ $id ]->next_assign_actors = $new_next_assign_json;
$formated_total_reviews[ $id ]->due_date = $new_due_date;
// Update the row.
$wpdb->update( $action_table, (array) $formated_total_reviews[ $id ], array(
"actor_id" => $formated_total_reviews[ $id ]->actor_id,
"action_history_id" => $formated_total_reviews[ $id ]->action_history_id
) );
// Clear reminder emails for this user on this action.
$ow_email_local->delete_step_email( intval( $r->action_history_id ), intval( $r->actor_id ) );
}
}
// No return here; let the normal tally/decision logic proceed now that no "assignment" remains.
}
$total_reviews = $formated_total_reviews;
error_log( "total_reviews (adjusted): " . print_r( $total_reviews, true ) );
}
}
return $total_reviews;
}, 10, 4); // priority 10, and 4 args
// [NEW] Per-assignee approval email
add_action( 'owf_individual_signoff_approved', 'send_individual_signoff_email', 10, 7 );
/**
* [NEW] Send "assignment-style" email when an individual assignee approves a review step.
* Reuses the Assignment Email template via get_step_mail_content(), without needing the next action_id.
*
* @param int $post_id
* @param int $action_history_id Current step action_history_id
* @param int $actor_id Approver user ID
* @param array $review_data Includes 'next_assign_actors' (json) and 'step_id' (proposed next step)
* @param int $current_step_id
* @param int $next_step_id Proposed next step id
*/
function send_individual_signoff_email( $post_id, $action_history_id, $actor_id, $review_data, $current_step_id, $next_step_id, $sign_off_comments ) {
$post_id = intval( $post_id );
$action_history_id = intval( $action_history_id );
$actor_id = intval( $actor_id );
$current_step_id = intval( $current_step_id );
$next_step_id = intval( $next_step_id );
$post = get_post( $post_id );
if ( ! $post ) {
return;
}
// Respect Assignment Emails on/off toggle.
$email_settings = get_option( 'oasiswf_email_settings' );
if ( ! is_array( $email_settings ) || ! isset( $email_settings['assignment_emails'] ) || $email_settings['assignment_emails'] !== 'yes' ) {
return;
}
$current_user_id = get_current_user_id();
$ow_email_settings_helper = new OW_Email_Settings_Helper();
$ow_email = new OW_Email();
// Candidate next assignees for this approval (may be empty).
$candidate_to_users = array();
if ( isset( $review_data['next_assign_actors'] ) && ! empty( $review_data['next_assign_actors'] ) ) {
$decoded = json_decode( $review_data['next_assign_actors'], true );
if ( is_array( $decoded ) ) {
$candidate_to_users = array_filter( array_map( 'intval', $decoded ) );
}
}
// Fallback: if we cannot determine next assignees yet, notify the post author.
if ( empty( $candidate_to_users ) ) {
$candidate_to_users = array( intval( $post->post_author ) );
}
// Use proposed next step id if present, otherwise fall back to current step id.
$step_for_template = $next_step_id > 0 ? $next_step_id : $current_step_id;
$review_data = array(
"comment" => $sign_off_comments,
"task_actor_id" => $actor_id
);
// Use the same comment accessor as assignment emails.
$comment = $ow_email->get_step_comment_content( $action_history_id, $review_data );
$post_id_for_params = $post_id; // clarity
foreach ( $candidate_to_users as $to_user_id ) {
$to_user_id = intval( $to_user_id );
if ( $to_user_id === $current_user_id ) {
continue; // mirror send_step_email: don't email the actor themselves
}
// Build the standard assignment email content using existing helper.
$mails = $ow_email->get_step_mail_content( $action_history_id, $step_for_template, $to_user_id, $post_id_for_params );
if ( ! $mails || empty( $mails->assign_subject ) || empty( $mails->assign_content ) ) {
continue;
}
$mail_content = $mails->assign_content . $comment;
$mail_content = apply_filters( "oasiswf_custom_email_content", $mail_content, $post_id_for_params, $action_history_id );
$email_cc = isset( $mails->assign_cc ) ? json_decode( json_encode( $mails->assign_cc ), true ) : array();
$email_bcc = isset( $mails->assign_bcc ) ? json_decode( json_encode( $mails->assign_bcc ), true ) : array();
$email_recipient_params = array(
"post_id" => $post_id_for_params,
"email_cc" => $email_cc,
"email_bcc" => $email_bcc
);
$recipients = $ow_email_settings_helper->get_cc_recipients( $email_recipient_params );
$cc_users = apply_filters( 'ow_assignment_cc_users', $recipients["email_cc"] );
$bcc_users = apply_filters( 'ow_assignment_bcc_users', $recipients["email_bcc"] );
// Send the email.
$ow_email->send_mail( $to_user_id, $mails->assign_subject, $mail_content, '', $cc_users, $bcc_users );
}
}
// [PATCH] Added custom placeholder "%last_signed_off_by%" for emails and its runtime resolver.
if ( ! function_exists( 'owf_register_last_signed_off_by_placeholder' ) ) {
function owf_register_last_signed_off_by_placeholder() {
// Show the placeholder label in the Step Info email editor (step-info-content.php)
add_filter( 'oasiswf_custom_placeholders', function( $placeholders ) {
if ( ! is_array( $placeholders ) ) {
$placeholders = array();
}
// [PATCH] Added new placeholder label so it appears in the dropdown
$placeholders['%last_signed_off_by%'] = esc_html__( 'Last Signed off by', 'oasisworkflow' );
return $placeholders;
} );
/**
* Inject the runtime value for assignment/reminder emails.
* This filter is called in the email sending pipeline with full context.
*
* @param array $mapping Existing custom placeholder map.
* @param int $action_id Current action history id (for the receiving assignment).
* @param int $step_id Current step id.
* @param int $to_user_id Recipient user id.
* @param int $post_id Post id.
*
* @return array
*/
add_filter( 'oasiswf_custom_placeholders_list', function( $mapping, $action_id, $step_id, $to_user_id, $post_id ) {
if ( ! is_array( $mapping ) ) {
$mapping = array();
}
// [PATCH] Compute "Last Signed off by" using the previous action (same logic as Inbox list).
$name = owf_get_last_signed_off_by( $action_id, $post_id );
if ( $name ) {
$mapping['%last_signed_off_by%'] = $name;
}
return $mapping;
}, 10, 5 );
}
owf_register_last_signed_off_by_placeholder();
}
function owf_get_last_signed_off_by( $action_id, $post_id ) {
$action_id = intval( $action_id );
$post_id = intval( $post_id );
if ( ! $action_id || ! $post_id ) {
return '';
}
$ow_history_service = new OW_History_Service();
$previous = $ow_history_service->get_action_history_by_id( $action_id ); // previous step/action
if ( ! $previous ) {
return '';
}
// Mirror class-ow-inbox-service.php get_table_rows() logic.
$comments = null;
if ( intval( $previous->assign_actor_id ) === -1 ) {
// Multi-assignee/review case: get review row with status "complete" for the previous action.
$review_rows = $ow_history_service->get_review_action_by_status( 'complete', $previous->ID );
if ( $review_rows ) {
$comments = json_decode( $review_rows[0]->comments );
}
}
if ( ! $comments ) {
$comments = json_decode( $previous->comment );
}
if ( is_array( $comments ) && ! empty( $comments[0]->send_id ) ) {
return OW_Utility::instance()->get_user_name( intval( $comments[0]->send_id ) );
}
return '';
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment