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 '
';
$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 ) . '
| ' . 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' ) . ' |
';
foreach ( $data[ $key ] as $name => $c ) {
echo '| ' . esc_html( $name ) . ' | ' . (int) $c[0] . ' | ' . (int) $c[1] . ' | ' . esc_html( round( 100 * $c[1] / max( 1, $c[0] ) ) . '%' ) . ' |
';
}
echo '
';
}
echo '
';
}
/**
* @param array $rows Label => count.
* @param int $total Base.
*/
private static function bars( $rows, $total ) {
echo '';
foreach ( $rows as $label => $count ) {
$pct = $total ? round( 100 * $count / $total ) : 0;
echo '- ' . esc_html( $label ) . '' . (int) $count . '' . (int) $pct . '%
';
}
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 );
}
}