Repository::POST_TYPE, 'post_status' => 'publish', 'posts_per_page' => 5000, 'fields' => 'ids', 'no_found_rows' => true, 'date_query' => array( array( 'after' => gmdate( 'Y-m-d H:i:s', $from ), 'before' => gmdate( 'Y-m-d H:i:s', $to ), 'inclusive' => true, ), ), // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query 'meta_query' => array( array( 'key' => 'mapp_version', 'value' => '3', ), ), ); if ( $lo_id ) { $args['meta_query'][] = array( 'key' => 'mapp_lo_id', 'value' => (string) $lo_id, ); } $ids = get_posts( $args ); $order = array( Repository::STAGE_STARTED, Repository::STAGE_CONTACT, Repository::STAGE_SUBMITTED, Repository::STAGE_FULL_STARTED, Repository::STAGE_FULL_SUBMITTED ); $out = array( 'total' => count( $ids ), 'stages' => array_fill_keys( $order, 0 ), 'steps' => array( 'Home Purchase' => array(), 'Home Refinance' => array() ), 'started' => array( 'Home Purchase' => 0, 'Home Refinance' => 0 ), 'by_lo' => array(), 'by_src' => array(), ); foreach ( $ids as $id ) { $stage = Repository::stage( $id ); $reached = array_search( $stage, $order, true ); foreach ( $order as $i => $s ) { if ( false !== $reached && $i <= $reached ) { ++$out['stages'][ $s ]; } } $submitted = false !== $reached && $reached >= 2; $purpose = (string) get_post_meta( $id, 'purpose', true ); if ( isset( $out['steps'][ $purpose ] ) ) { ++$out['started'][ $purpose ]; $seen = get_post_meta( $id, 'mapp_steps_seen', true ); foreach ( is_array( $seen ) ? array_keys( $seen ) : array() as $step ) { $out['steps'][ $purpose ][ $step ] = isset( $out['steps'][ $purpose ][ $step ] ) ? $out['steps'][ $purpose ][ $step ] + 1 : 1; } } $lo = (int) get_post_meta( $id, 'mapp_lo_id', true ); $key = $lo ? get_the_title( $lo ) : __( '(site)', '1003-mortgage-application' ); self::bump( $out['by_lo'], $key, $submitted ); $src = self::source( $id ); self::bump( $out['by_src'], '' !== $src ? $src : __( '(direct)', '1003-mortgage-application' ), $submitted ); } return $out; } /** * Where an application came from: the UTM source, else the referring site's host. * * @param int $id Application ID. * @return string Empty for direct visits. */ public static function source( $id ) { $src = (string) get_post_meta( $id, 'mapp_utm_source', true ); if ( '' === $src ) { $ref = (string) get_post_meta( $id, 'mapp_referrer', true ); $src = '' !== $ref ? (string) wp_parse_url( $ref, PHP_URL_HOST ) : ''; } return $src; } /** * @param array $bucket Bucket. * @param string $key Key. * @param bool $submitted Submitted. */ private static function bump( &$bucket, $key, $submitted ) { if ( ! isset( $bucket[ $key ] ) ) { $bucket[ $key ] = array( 0, 0 ); } ++$bucket[ $key ][0]; if ( $submitted ) { ++$bucket[ $key ][1]; } } /** Page. */ public static function render() { // phpcs:disable WordPress.Security.NonceVerification.Recommended -- read-only report filters. $days = isset( $_GET['days'] ) ? max( 1, min( 365, (int) $_GET['days'] ) ) : 30; $lo_id = isset( $_GET['lo'] ) ? (int) $_GET['lo'] : 0; // phpcs:enable $data = self::data( time() - $days * DAY_IN_SECONDS, time(), $lo_id ); ob_start(); echo '
'; echo ''; self::lo_select( 'lo', $lo_id ); echo '
'; $filters = (string) ob_get_clean(); echo '
'; Ui::head( __( 'Funnel', '1003-mortgage-application' ), __( 'Where borrowers stop. Counts applications started in the period; a step counts once a borrower reached it.', '1003-mortgage-application' ), $filters ); if ( ! $data['total'] ) { echo '

' . esc_html__( 'No 3.0 applications in this period yet.', '1003-mortgage-application' ) . '

'; return; } // Stage counts are cumulative: an application counts in every stage it reached. $submitted = $data['stages']['submitted']; echo '
'; foreach ( array( __( 'Started', '1003-mortgage-application' ) => number_format_i18n( $data['total'] ), __( 'Short app submitted', '1003-mortgage-application' ) => number_format_i18n( $submitted ), __( 'Submit rate', '1003-mortgage-application' ) => round( 100 * $submitted / max( 1, $data['total'] ) ) . '%', __( 'Full 1003 submitted', '1003-mortgage-application' ) => number_format_i18n( $data['stages']['full_submitted'] ), ) as $label => $value ) { echo '
' . esc_html( $label ) . '' . esc_html( $value ) . '
'; } echo '
'; $labels = array( 'started' => __( 'Started', '1003-mortgage-application' ), 'contact' => __( 'In progress (contact saved)', '1003-mortgage-application' ), 'submitted' => __( 'Short app submitted', '1003-mortgage-application' ), 'full_started' => __( 'Full 1003 started', '1003-mortgage-application' ), 'full_submitted' => __( 'Full 1003 submitted', '1003-mortgage-application' ), ); echo '

' . esc_html__( 'How far applications got', '1003-mortgage-application' ) . '

'; self::bars( array_combine( array_values( $labels ), array_values( $data['stages'] ) ), $data['total'] ); foreach ( array( 'Home Purchase' => __( 'Purchase: share of starts reaching each step', '1003-mortgage-application' ), 'Home Refinance' => __( 'Refinance: share of starts reaching each step', '1003-mortgage-application' ) ) as $purpose => $title ) { if ( ! $data['started'][ $purpose ] ) { continue; } $rows = array(); foreach ( Schema::visible_steps( array( 'purpose' => $purpose, 'mortgage_balance' => '1', 'refi_goal' => 'Cash out', 'property_use' => 'Investment Property' ) ) as $step ) { if ( 'done' === $step['kind'] || 'purpose' === $step['id'] ) { continue; } $label = $step['id']; if ( ! empty( $step['title'] ) ) { $label = Schema::text( $step['title'] ); } elseif ( ! empty( $step['questions'] ) && Schema::question( $step['questions'][0] ) ) { $label = Schema::text( Schema::question( $step['questions'][0] )['label'] ); } elseif ( 'reveal' === $step['kind'] ) { $label = __( 'Numbers screen', '1003-mortgage-application' ); } if ( 'contact' === $step['kind'] ) { $label = __( 'Contact details', '1003-mortgage-application' ); } $rows[ $label ] = isset( $data['steps'][ $purpose ][ $step['id'] ] ) ? $data['steps'][ $purpose ][ $step['id'] ] : 0; } echo '

' . esc_html( $title ) . '

'; self::bars( $rows, $data['started'][ $purpose ] ); } echo '
'; foreach ( array( 'by_lo' => __( 'By loan officer', '1003-mortgage-application' ), 'by_src' => __( 'By source', '1003-mortgage-application' ) ) as $key => $title ) { uasort( $data[ $key ], function ( $a, $b ) { return $b[0] - $a[0]; } ); echo '

' . esc_html( $title ) . '

'; foreach ( $data[ $key ] as $name => $c ) { echo ''; } echo '
' . esc_html( 'by_lo' === $key ? __( 'Loan officer', '1003-mortgage-application' ) : __( 'Source', '1003-mortgage-application' ) ) . '' . esc_html__( 'Started', '1003-mortgage-application' ) . '' . esc_html__( 'Submitted', '1003-mortgage-application' ) . '' . esc_html__( 'Rate', '1003-mortgage-application' ) . '
' . esc_html( $name ) . '' . (int) $c[0] . '' . (int) $c[1] . '' . esc_html( round( 100 * $c[1] / max( 1, $c[0] ) ) . '%' ) . '
'; } echo '
'; } /** * @param array $rows Label => count. * @param int $total Base. */ private static function bars( $rows, $total ) { echo '
'; } /** * @param string $name Field name. * @param int $selected Selected LO. */ private static function lo_select( $name, $selected ) { $los = get_posts( array( 'post_type' => LoanOfficers::POST_TYPE, 'post_status' => 'publish', 'posts_per_page' => 500, 'orderby' => 'title', 'order' => 'ASC', ) ); if ( ! $los ) { return; } echo ' '; } /** * @param array $columns Columns. * @return array */ public static function columns( $columns ) { // Stage replaces 2.6's percent "Status"; author and the "Published" // date said nothing about an application. $new = array( 'cb' => isset( $columns['cb'] ) ? $columns['cb'] : '', 'title' => __( 'Applicant', '1003-mortgage-application' ), 'mapp_stage' => __( 'Stage', '1003-mortgage-application' ), 'loanType' => __( 'Type', '1003-mortgage-application' ), 'mapp_lo' => __( 'Loan officer', '1003-mortgage-application' ), 'mapp_source' => __( 'Source', '1003-mortgage-application' ), 'mapp_started' => __( 'Started', '1003-mortgage-application' ), ); return $new; } /** * @param array $columns Sortable columns. * @return array */ public static function sortable( $columns ) { unset( $columns['status'] ); $columns['mapp_started'] = array( 'date', true ); return $columns; } /** * @param string $column Column. * @param int $post_id Post ID. */ public static function column( $column, $post_id ) { switch ( $column ) { case 'mapp_stage': $labels = array( 'started' => __( 'Started', '1003-mortgage-application' ), 'contact' => __( 'In progress (contact saved)', '1003-mortgage-application' ), 'submitted' => __( 'Submitted', '1003-mortgage-application' ), 'full_started' => __( 'Full 1003 in progress', '1003-mortgage-application' ), 'full_submitted' => __( 'Full 1003 submitted', '1003-mortgage-application' ), ); if ( '' === (string) get_post_meta( $post_id, 'mapp_version', true ) ) { $status = (int) get_post_meta( $post_id, 'application_status', true ); echo esc_html( 100 <= $status ? __( 'Submitted', '1003-mortgage-application' ) : $status . '%' ); } else { $stage = Repository::stage( $post_id ); echo esc_html( isset( $labels[ $stage ] ) ? $labels[ $stage ] : $stage ); } break; case 'mapp_lo': $lo = (int) get_post_meta( $post_id, 'mapp_lo_id', true ); echo esc_html( $lo ? get_the_title( $lo ) : '—' ); break; case 'mapp_source': $src = self::source( $post_id ); echo esc_html( '' !== $src ? $src : '—' ); break; case 'mapp_started': $time = (int) get_post_time( 'U', true, $post_id ); echo '' . esc_html( wp_date( 'm-d-Y', $time ) ) . ''; break; } } /** * @param string $post_type Post type. */ public static function lo_filter( $post_type ) { if ( Repository::POST_TYPE === $post_type ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- list filter. self::lo_select( 'mapp_lo', isset( $_GET['mapp_lo'] ) ? (int) $_GET['mapp_lo'] : 0 ); } } /** * @param \WP_Query $query Query. */ public static function apply_lo_filter( $query ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- list filter. if ( ! is_admin() || ! $query->is_main_query() || Repository::POST_TYPE !== $query->get( 'post_type' ) || empty( $_GET['mapp_lo'] ) ) { return; } $meta = (array) $query->get( 'meta_query' ); $meta[] = array( 'key' => 'mapp_lo_id', 'value' => (string) (int) $_GET['mapp_lo'], // phpcs:ignore WordPress.Security.NonceVerification.Recommended ); $query->set( 'meta_query', $meta ); } }