ID;
$answers = Repository::answers( $id, false );
$groups = array(
'short' => __( 'Short application', '1003-mortgage-application' ),
'contact' => __( 'Contact', '1003-mortgage-application' ),
'final' => __( 'Final questions', '1003-mortgage-application' ),
'full' => __( 'Full 1003', '1003-mortgage-application' ),
);
foreach ( $groups as $stage => $title ) {
$rows = '';
foreach ( Schema::questions_in( $stage ) as $key => $def ) {
if ( ! empty( $def['sensitive'] ) ) {
$stored = (string) get_post_meta( $id, $key, true );
if ( '' === $stored ) {
continue;
}
$reveal = self::can_reveal()
? ' ' . esc_html__( 'Reveal', '1003-mortgage-application' ) . ' '
: ' ' . esc_html__( '(administrators can reveal)', '1003-mortgage-application' ) . ' ';
$rows .= '
' . esc_html( Schema::text( $def['label'] ) ) . ' •••-••-•••• ' . $reveal . ' ';
continue;
}
if ( ! isset( $answers[ $key ] ) ) {
continue;
}
$value = Schema::display_value( $key, $answers[ $key ] );
if ( '' === $value ) {
continue;
}
// Short LO labels; consent wording lives in the consent log.
$label = Schema::admin_label( $key );
$rows .= '' . esc_html( $label ) . ' ' . esc_html( $value ) . ' ';
}
if ( $rows ) {
echo '' . esc_html( $title ) . ' '; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- rows escaped above.
}
}
// 2.6 fields no longer asked, shown when present on older applications.
$legacy = '';
foreach ( Schema::questions() as $key => $def ) {
if ( empty( $def['legacy'] ) ) {
continue;
}
$value = get_post_meta( $id, $key, true );
if ( '' !== $value && null !== $value ) {
$legacy .= '' . esc_html( Schema::text( $def['label'] ) ) . ' ' . esc_html( is_scalar( $value ) ? $value : wp_json_encode( $value ) ) . ' ';
}
}
if ( $legacy ) {
echo '' . esc_html__( 'Earlier version answers', '1003-mortgage-application' ) . ' '; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
$nonce = wp_create_nonce( 'mapp_reveal_' . $id );
?>
post_type ) {
self::render_upload_link( $post );
}
$files = Store::files_for( $post->ID );
if ( ! $files ) {
echo '' . esc_html__( 'No documents uploaded with the application.', '1003-mortgage-application' ) . '
';
} else {
self::files_table( $post->ID, $files, $nonce );
}
if ( 'mortgage_application' === $post->post_type ) {
self::render_linked_uploads( $post->ID, $nonce );
}
}
/**
* Upload-page records matched to this application by email.
*
* @param int $app_id Application ID.
* @param string $nonce REST nonce.
*/
private static function render_linked_uploads( $app_id, $nonce ) {
// The upload page is a Pro feature; the free build has no Portal.
$uploads = class_exists( '\Mapp\Files\Portal' ) ? \Mapp\Files\Portal::linked_uploads( $app_id ) : array();
if ( ! $uploads ) {
return;
}
echo '' . esc_html__( 'From the upload page (not verified)', '1003-mortgage-application' ) . ' ';
echo '' . esc_html__( 'Sent from the document upload page with this application\'s email. Anyone who knows the email can use that page, so confirm with the borrower before relying on these.', '1003-mortgage-application' ) . '
';
foreach ( $uploads as $upload_id ) {
$files = Store::files_for( $upload_id );
if ( ! $files ) {
continue;
}
$lo = (int) get_post_meta( $upload_id, 'mapp_lo_id', true );
echo '' . esc_html( get_the_title( $upload_id ) ) . ' ' . esc_html( (string) get_post_meta( $upload_id, 'file_uploads_email', true ) . ( $lo ? ' · ' . sprintf( /* translators: %s: LO */ __( 'sent to %s', '1003-mortgage-application' ), get_the_title( $lo ) ) : '' ) ) . '
';
self::files_table( $upload_id, $files, $nonce );
}
}
/**
* File Uploads list: which application and LO each upload went to.
*
* @param array $columns Columns.
* @return array
*/
public static function upload_columns( $columns ) {
$out = array();
foreach ( $columns as $key => $label ) {
$out[ $key ] = $label;
if ( 'title' === $key ) {
$out['mapp_upload_app'] = __( 'Application', '1003-mortgage-application' );
$out['mapp_upload_lo'] = __( 'Loan officer', '1003-mortgage-application' );
}
}
return $out;
}
/**
* @param string $column Column.
* @param int $post_id Upload record ID.
*/
public static function upload_column( $column, $post_id ) {
if ( 'mapp_upload_app' === $column ) {
$app_id = (int) get_post_meta( $post_id, 'mapp_application_id', true );
echo $app_id ? '' . esc_html( get_the_title( $app_id ) ) . ' ' : '—';
} elseif ( 'mapp_upload_lo' === $column ) {
$lo = (int) get_post_meta( $post_id, 'mapp_lo_id', true );
echo esc_html( $lo ? get_the_title( $lo ) : '—' );
}
}
/**
* On an upload record: the application it was matched to.
*
* @param \WP_Post $post Upload record.
*/
private static function render_upload_link( $post ) {
$app_id = (int) get_post_meta( $post->ID, 'mapp_application_id', true );
$lo = (int) get_post_meta( $post->ID, 'mapp_lo_id', true );
echo '';
echo '' . esc_html__( 'Application', '1003-mortgage-application' ) . ' ' . ( $app_id ? '' . esc_html( get_the_title( $app_id ) ) . ' ' . esc_html__( '(matched by email, not verified)', '1003-mortgage-application' ) . ' ' : esc_html__( 'None uses this email', '1003-mortgage-application' ) ) . ' ';
echo '' . esc_html__( 'Loan officer', '1003-mortgage-application' ) . ' ' . esc_html( $lo ? get_the_title( $lo ) . ( 'yes' === get_post_meta( $post->ID, 'mapp_lo_chosen', true ) ? ' ' . __( '(chosen by the borrower)', '1003-mortgage-application' ) : '' ) : __( 'Office recipients', '1003-mortgage-application' ) ) . ' ';
echo ' ';
}
/**
* @param int $id Record holding the files.
* @param array $files Files.
* @param string $nonce REST nonce.
*/
private static function files_table( $id, $files, $nonce ) {
$ghl = self::ghl_on();
echo '' . esc_html__( 'File', '1003-mortgage-application' ) . ' ' . esc_html__( 'For', '1003-mortgage-application' ) . ' ' . esc_html__( 'Uploaded', '1003-mortgage-application' ) . ' ' . ( $ghl ? '' . esc_html__( 'GHL', '1003-mortgage-application' ) . ' ' : '' ) . '' . esc_html__( 'Downloads', '1003-mortgage-application' ) . ' ';
foreach ( $files as $file_id => $file ) {
$url = add_query_arg( '_wpnonce', $nonce, rest_url( 'mapp/v1/files/' . (int) $id . '/' . rawurlencode( $file_id ) ) );
printf(
'%s %s %s %s %s%d ',
esc_url( $url ),
esc_html( $file['name'] ),
esc_html( size_format( $file['size'] ) ),
esc_html( self::doc_label( $file['doc_type'] ) ),
esc_html( self::date( $file['uploaded'] ) ),
$ghl ? '' . esc_html( isset( $file['crm_sent'] ) ? ( 'upload' === $file['crm_sent'] ? __( 'Attached', '1003-mortgage-application' ) : __( 'Link in note', '1003-mortgage-application' ) ) : '—' ) . ' ' : '', // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- escaped here.
(int) $file['downloads']
);
}
echo '
';
}
/**
* @param \WP_Post $post Post.
*/
public static function render_activity( $post ) {
$sections = array(
__( 'Deliveries', '1003-mortgage-application' ) => array_map(
function ( $e ) {
return array( self::date( $e['time'] ), $e['action'] . ' · ' . $e['milestone'], $e['error'] ? $e['error'] : ( $e['code'] ? 'OK (' . $e['code'] . ')' : 'OK' ) );
},
Log::entries( $post->ID )
),
__( 'Consent', '1003-mortgage-application' ) => array_map(
function ( $e ) {
return array( self::date( $e['time'] ), $e['kind'] . ( $e['given'] ? '' : ' (withdrawn)' ) . ' · ' . $e['ip'], $e['text'] );
},
Consent::entries( $post->ID )
),
__( 'Access', '1003-mortgage-application' ) => array_map(
function ( $e ) {
$actor = $e['actor'];
if ( 0 === strpos( $actor, 'user:' ) ) {
$user = get_userdata( (int) substr( $actor, 5 ) );
$actor = $user ? $user->user_login : $actor;
}
return array( self::date( $e['time'] ), $e['action'] . ' · ' . $e['target'], $actor . ' · ' . $e['ip'] );
},
Access::entries( $post->ID )
),
);
foreach ( $sections as $title => $rows ) {
echo '' . esc_html( $title ) . ' ';
if ( ! $rows ) {
echo '' . esc_html__( 'Nothing yet.', '1003-mortgage-application' ) . '
';
continue;
}
echo '';
foreach ( array_reverse( $rows ) as $row ) {
echo '' . esc_html( $row[0] ) . ' ' . esc_html( $row[1] ) . ' ' . esc_html( $row[2] ) . ' ';
}
echo '
';
}
}
/**
* @param \WP_Post $post Post.
*/
public static function render_actions( $post ) {
$id = (int) $post->ID;
$stage = Repository::stage( $id );
$labels = array(
'started' => __( 'Started (no contact details yet)', '1003-mortgage-application' ),
'contact' => __( 'In progress (contact saved)', '1003-mortgage-application' ),
'submitted' => __( 'Short app submitted', '1003-mortgage-application' ),
'full_started' => __( 'Full 1003 in progress', '1003-mortgage-application' ),
'full_submitted' => __( 'Full 1003 submitted', '1003-mortgage-application' ),
);
$lo = LoanOfficers::profile( (int) get_post_meta( $id, 'mapp_lo_id', true ), true );
echo '' . esc_html( isset( $labels[ $stage ] ) ? $labels[ $stage ] : $stage ) . ' ';
echo esc_html__( 'Last step:', '1003-mortgage-application' ) . ' ' . esc_html( self::step_label( (string) get_post_meta( $id, 'mapp_last_step', true ) ) ) . '
';
echo '' . esc_html__( 'Loan officer:', '1003-mortgage-application' ) . ' ' . esc_html( $lo ? $lo['name'] : '—' ) . ' ';
$contact = (string) get_post_meta( $id, 'mapp_crm_contact_id', true );
if ( self::ghl_on() || '' !== $contact ) {
echo esc_html__( 'GHL contact:', '1003-mortgage-application' ) . ' ' . esc_html( '' !== $contact ? $contact : '—' ) . ' ';
}
$source = (string) get_post_meta( $id, 'mapp_utm_source', true );
echo esc_html__( 'Source:', '1003-mortgage-application' ) . ' ' . esc_html( '' !== $source ? $source : ( (string) get_post_meta( $id, 'mapp_referrer', true ) ?: '—' ) ) . '
';
// Reminders: what went out, and what is next.
$rem = \Mapp\Application\Reminders::status( $id );
$lines = array();
foreach ( $rem['sent'] as $row ) {
/* translators: 1: reminder, 2: date */
$lines[] = sprintf( __( '%1$s sent %2$s', '1003-mortgage-application' ), $row[0], self::date( $row[1] ) );
}
if ( $rem['next'] ) {
/* translators: 1: reminder, 2: date */
$lines[] = sprintf( __( '%1$s due %2$s', '1003-mortgage-application' ), $rem['next_label'], self::date( max( $rem['next'], time() ) ) );
}
echo '' . esc_html__( 'Reminders:', '1003-mortgage-application' ) . ' ' . ( $lines ? ' ' . implode( ' ', array_map( 'esc_html', $lines ) ) : esc_html__( 'none', '1003-mortgage-application' ) ) . '
';
foreach (
array(
'resend' => __( 'Resend to emails and connected services', '1003-mortgage-application' ),
'resume' => __( 'Email borrower a resume link', '1003-mortgage-application' ),
'revoke' => __( 'Revoke resume links', '1003-mortgage-application' ),
) as $do => $label
) {
$url = wp_nonce_url( admin_url( 'admin-post.php?action=mapp_app_action&do=' . $do . '&post=' . $id ), 'mapp_app_action_' . $id );
$confirm = array(
'resend' => __( 'Send this application to the email recipients and connected services again? The borrower may get their receipt again.', '1003-mortgage-application' ),
'revoke' => __( 'Stop every resume link for this application? The borrower will need a new link to continue.', '1003-mortgage-application' ),
);
$onclick = isset( $confirm[ $do ] ) ? ' onclick="return confirm(' . esc_attr( wp_json_encode( $confirm[ $do ] ) ) . ');"' : '';
echo '' . esc_html( $label ) . '
'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- parts escaped above.
}
}
/** admin-post handler for the action buttons. */
public static function handle_action() {
$id = isset( $_GET['post'] ) ? (int) $_GET['post'] : 0;
check_admin_referer( 'mapp_app_action_' . $id );
if ( ! $id || ! current_user_can( 'edit_post', $id ) || ! Repository::exists( $id ) ) {
wp_die( esc_html__( 'Not allowed.', '1003-mortgage-application' ), 403 );
}
$do = isset( $_GET['do'] ) ? sanitize_key( wp_unslash( $_GET['do'] ) ) : '';
switch ( $do ) {
case 'resend':
$stage = Repository::stage( $id );
$map = array(
'contact' => 'contact',
'submitted' => 'submitted',
'full_started' => 'submitted',
'full_submitted' => 'full_submitted',
);
if ( isset( $map[ $stage ] ) ) {
$m = $map[ $stage ];
foreach ( array( 'mapp_milestone_', 'mapp_dispatched_', 'mapp_crm_note_', 'mapp_crm_sent_', 'mapp_crm_attempts_' ) as $prefix ) {
delete_post_meta( $id, $prefix . $m );
}
// A fresh connector source_id, or the connector treats it as a duplicate.
update_post_meta( $id, 'mapp_crm_resend_' . $m, (int) get_post_meta( $id, 'mapp_crm_resend_' . $m, true ) + 1 );
Events::milestone( $id, $m );
}
break;
case 'resume':
Mailer::resume_link( $id, Controller::resume_url( $id, Tokens::issue( $id, 'link' ) ) );
break;
case 'revoke':
Tokens::revoke_all( $id );
break;
}
wp_safe_redirect( add_query_arg( 'mapp_done', $do, get_edit_post_link( $id, 'url' ) ) );
exit;
}
/** Confirmation after an action. */
public static function action_notice() {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- display only.
$done = isset( $_GET['mapp_done'] ) ? sanitize_key( wp_unslash( $_GET['mapp_done'] ) ) : '';
$text = array(
'resend' => __( 'Queued: emails and connected services will run again.', '1003-mortgage-application' ),
'resume' => __( 'Resume link emailed to the borrower.', '1003-mortgage-application' ),
'revoke' => __( 'All resume links for this application now stop working.', '1003-mortgage-application' ),
);
if ( isset( $text[ $done ] ) ) {
echo '' . esc_html( $text[ $done ] ) . '
';
}
}
/**
* The form step an id refers to, in words.
*
* @param string $step Step id.
* @return string
*/
private static function step_label( $step ) {
if ( '' === $step ) {
return '—';
}
if ( 0 === strpos( $step, 'full_' ) ) {
foreach ( Schema::get()['sections'] as $sec ) {
if ( 'full_' . $sec['id'] === $step ) {
return __( 'Full 1003', '1003-mortgage-application' ) . ': ' . Schema::text( $sec['title'] );
}
}
}
foreach ( Schema::get()['steps'] as $s ) {
if ( $s['id'] !== $step ) {
continue;
}
if ( ! empty( $s['title'] ) ) {
return Schema::text( $s['title'] );
}
if ( ! empty( $s['questions'] ) ) {
return Schema::admin_label( $s['questions'][0] );
}
$kinds = array(
'reveal' => __( 'Numbers screen', '1003-mortgage-application' ),
'contact' => __( 'Contact details', '1003-mortgage-application' ),
);
return isset( $kinds[ $s['kind'] ] ) ? $kinds[ $s['kind'] ] : $step;
}
return $step;
}
/**
* Checklist item names (same list as the form).
*
* @param string $type Doc type.
* @return string
*/
private static function doc_label( $type ) {
return Store::doc_label( $type );
}
/**
* MM-DD-YYYY h:mm am, site time.
*
* @param int $time Timestamp.
* @return string
*/
private static function date( $time ) {
return $time ? wp_date( 'm-d-Y g:i a', (int) $time ) : '';
}
}