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__( '(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 ) . '

' . $rows . '
'; // 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' ) . '

' . $legacy . '
'; // 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 '' . ( $ghl ? '' : '' ) . ''; foreach ( $files as $file_id => $file ) { $url = add_query_arg( '_wpnonce', $nonce, rest_url( 'mapp/v1/files/' . (int) $id . '/' . rawurlencode( $file_id ) ) ); printf( '%s', 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 ? '' : '', // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- escaped here. (int) $file['downloads'] ); } echo '
' . esc_html__( 'File', '1003-mortgage-application' ) . '' . esc_html__( 'For', '1003-mortgage-application' ) . '' . esc_html__( 'Uploaded', '1003-mortgage-application' ) . '' . esc_html__( 'GHL', '1003-mortgage-application' ) . '' . esc_html__( 'Downloads', '1003-mortgage-application' ) . '
%s %s%s%s%d
' . esc_html( isset( $file['crm_sent'] ) ? ( 'upload' === $file['crm_sent'] ? __( 'Attached', '1003-mortgage-application' ) : __( 'Link in note', '1003-mortgage-application' ) ) : '—' ) . '
'; } /** * @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 ''; } echo '
' . esc_html( $row[0] ) . '' . esc_html( $row[1] ) . '' . esc_html( $row[2] ) . '
'; } } /** * @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 ) : ''; } }