'any', 'per_page' => 1, ) )['total'] > 0; // Visits are counted from the first one on, so the report fills before any form is sent. $has_data = $has_leads || Visits::has_data() || Events::has_data(); Shell::open( 'overview', __( 'Overview', 'dealroot' ), array( 'description' => __( 'Where your enquiries come from and how many of them become customers.', 'dealroot' ), 'actions' => self::live_link() . ( $has_data ? $this->period_picker( $state ) : '' ) . Shell::button( __( 'Add lead', 'dealroot' ), Shell::url( Admin::PAGE, array( 'lead' => 'new' ) ), 'primary' ), ) ); if ( ! $has_data ) { $this->issues_panel(); ( new Onboarding() )->render(); Shell::close(); return; } $breakdown = Reports::breakdown( $state['period'], $state['dimension'], $state['model'] ); /** * Filters the totals above the report, e.g. to add the advertising costs of the period. * * @param array $totals Totals: leads, won, revenue, profit, clicks, visits, cost. * @param string $period Period key. * @param string $dimension Dimension of the report. */ $totals = (array) apply_filters( 'dealroot_report_totals', Reports::totals( $breakdown ), $state['period'], $state['dimension'] ); $totals = array_merge( Reports::empty_row(), $totals ); $forms = $this->form_rows( $state ); $this->kpis( $totals, $state ); // Before the first lead: the visits by source, the forms visitors started, and the first // steps until they are done or hidden. if ( ! $has_leads ) { $this->report( $breakdown, $state ); $this->map( $state ); $this->issues_panel(); if ( $forms ) { $this->forms( $forms ); } ( new Onboarding() )->render( true ); Shell::close(); return; } // What needs doing now comes first, then where the enquiries come from. $this->attention(); /** * Fires at the top of the overview, below what needs attention, e.g. the time away. * * @param array $state Period and report settings. */ do_action( 'dealroot_overview_top', $state ); // The report gets the full width, so the columns with costs fit next to the others. $this->report( $breakdown, $state ); // The short answers side by side in rows of cards, the question to the assistant below. ?>
insights(); $this->funnel( $state ); $this->impact(); if ( ! $forms ) { $this->latest(); } ?>
map( $state ); if ( $forms ) { ?>
forms( $forms ); ?>
latest(); ?>
Admin::LEADS_PAGE ) ); $points = array(); $cities = false; foreach ( $places as $place ) { $city = '' !== $place['city']; $cities = $cities || $city; $dimension = $city ? 'city' : 'country'; $points[] = array( 'c' => $place['country'], 'name' => $city ? $place['city'] : Geo::country_name( $place['country'] ), 'area' => $city ? Geo::country_name( $place['country'] ) : '', 'lat' => $place['lat'], 'lon' => $place['lon'], 'v' => $place['visits'], 'l' => $place['leads'], 'w' => $place['won'], 'url' => $place['leads'] > 0 ? add_query_arg( array_merge( $list, array( 'dim' => $dimension, 'group_value' => rawurlencode( $city ? $place['city'] : $place['country'] ), ) ), admin_url( 'admin.php' ) ) : '', ); } usort( $points, static function ( $a, $b ) { return ( $b['v'] + $b['l'] ) <=> ( $a['v'] + $a['l'] ); } ); ?>

    ' . esc_html__( 'Turn on cities', 'dealroot' ) . '' ); ?>

    ' . esc_html( sprintf( /* translators: %s: number of visitors */ __( '%s on the site now', 'dealroot' ), number_format_i18n( $count ) ) ) . ''; } /** * Choice of the period, kept for the next visit. * * @param array $state Report state. * @return string HTML. */ private function period_picker( array $state ) { $base = Report_State::url_args( false ); ob_start(); ?>
    0; $has_profit = null !== Reports::default_margin() && array_sum( $totals['revenue'] ) > 0; $response = Reports::response_time( $state['period'] ); $more = array(); $detail = array(); foreach ( Events::types() as $type => $label ) { if ( ! empty( $totals['clicks'][ $type ] ) ) { $detail[] = $label . ' ' . number_format_i18n( $totals['clicks'][ $type ] ); } } ?>
    change( $totals['leads'], $state['period'] ); ?>
    0 && '—' !== Format::per( $totals['revenue'], $totals['won'] ) ) : ?>
    = $totals['leads'] && $visits > 0 ) : ?>
    = $totals['leads'] && $visits > 0 ) ) { $more[ __( 'Contact clicks', 'dealroot' ) ] = number_format_i18n( $clicks ); } $hints = array(); if ( $has_cost ) { $spent = Format::money_list( $totals['cost'] ); $cpl = Format::per( $totals['cost'], $totals['leads'] ); $cpc = Format::per( $totals['cost'], $totals['won'] ); $more[ __( 'Cost per lead', 'dealroot' ) ] = $cpl; $hints[ __( 'Cost per lead', 'dealroot' ) ] = array( 'rows' => array( array( __( 'Ad costs', 'dealroot' ), $spent ), array( '÷ ' . __( 'Enquiries', 'dealroot' ), number_format_i18n( $totals['leads'] ) ), ), 'result' => array( '= ' . __( 'Cost per lead', 'dealroot' ), $cpl ), 'note' => __( 'What one enquiry cost, whether it became a customer or not.', 'dealroot' ), ); $more[ __( 'Cost per customer', 'dealroot' ) ] = $cpc; $hints[ __( 'Cost per customer', 'dealroot' ) ] = array( 'rows' => array( array( __( 'Ad costs', 'dealroot' ), $spent ), array( '÷ ' . __( 'Customers', 'dealroot' ), number_format_i18n( $totals['won'] ) ), ), 'result' => array( '= ' . __( 'Cost per customer', 'dealroot' ), $cpc ), /* translators: %s: share such as 68% */ 'note' => sprintf( __( 'What one buyer cost. %s of the enquiries became customers.', 'dealroot' ), Format::percent( $totals['won'], $totals['leads'] ) ), ); } if ( $has_profit ) { $profit = Format::money_list( $totals['profit'] ); $more[ __( 'Gross profit', 'dealroot' ) ] = $profit; $hints[ __( 'Gross profit', 'dealroot' ) ] = array( 'rows' => array( array( __( 'Revenue', 'dealroot' ), Format::money_list( $totals['revenue'] ) ), ), 'result' => array( '= ' . __( 'Gross profit', 'dealroot' ), $profit ), 'note' => __( 'What is left of the revenue after the cost of the goods or the work, by your margins.', 'dealroot' ), ); if ( $has_cost ) { $poas = Format::roas( $totals['profit'], $totals['cost'] ); $more[ __( 'Profit on ad spend', 'dealroot' ) ] = $poas; $hints[ __( 'Profit on ad spend', 'dealroot' ) ] = array( 'rows' => array( array( __( 'Gross profit', 'dealroot' ), $profit ), array( '÷ ' . __( 'Ad costs', 'dealroot' ), $spent ), ), 'result' => array( '= ' . __( 'Profit on ad spend', 'dealroot' ), $poas ), 'note' => __( 'Below 1× the ads cost more than they earn.', 'dealroot' ), ); } } $more = array_filter( $more, static function ( $value ) { return '—' !== $value; } ); ?>
    $value ) : ?>
    $from, 'to' => $to, 'per_page' => 1, ) )['total']; if ( $before < 1 ) { return; } $change = (int) round( ( $leads - $before ) / $before * 100 ); $diff = $leads - $before; // A single day has few leads: the difference says more than a percentage. if ( in_array( $period, array( 'today', 'yesterday' ), true ) ) { $class = $diff > 0 ? 'is-up' : ( $diff < 0 ? 'is-down' : 'is-flat' ); if ( 'today' === $period ) { /* translators: %s: number of leads */ $text = $diff > 0 ? sprintf( _n( '%s more than yesterday by this time', '%s more than yesterday by this time', $diff, 'dealroot' ), number_format_i18n( $diff ) ) : ( $diff < 0 ? sprintf( /* translators: %s: number of leads */ _n( '%s fewer than yesterday by this time', '%s fewer than yesterday by this time', -$diff, 'dealroot' ), number_format_i18n( -$diff ) ) : __( 'As many as yesterday by this time', 'dealroot' ) ); } else { /* translators: %s: number of leads */ $text = $diff > 0 ? sprintf( _n( '%s more than the day before', '%s more than the day before', $diff, 'dealroot' ), number_format_i18n( $diff ) ) : ( $diff < 0 ? sprintf( /* translators: %s: number of leads */ _n( '%s fewer than the day before', '%s fewer than the day before', -$diff, 'dealroot' ), number_format_i18n( -$diff ) ) : __( 'As many as the day before', 'dealroot' ) ); } } elseif ( $change > 0 ) { $class = 'is-up'; /* translators: %s: change such as 12% */ $text = sprintf( __( '%s more than the period before', 'dealroot' ), number_format_i18n( $change ) . '%' ); } elseif ( $change < 0 ) { $class = 'is-down'; /* translators: %s: change such as 12% */ $text = sprintf( __( '%s fewer than the period before', 'dealroot' ), number_format_i18n( abs( $change ) ) . '%' ); } else { $class = 'is-flat'; $text = __( 'As many as the period before', 'dealroot' ); } ?> 'critical' === $issue['severity'] ? 'alert' : 'warning', 'title' => $issue['label'], 'text' => $issue['description'], 'url' => $issue['url'], 'action' => $issue['action'] ?? __( 'Open settings', 'dealroot' ), ); } $new = Leads::status_counts()['new']; $waiting = $new ? Leads::query( array( 'status' => 'new', 'to' => gmdate( 'Y-m-d H:i:s', time() - DAY_IN_SECONDS ), 'per_page' => 1, ) )['total'] : 0; if ( $new > 0 ) { $items[] = array( 'tone' => $waiting ? 'alert' : 'info', 'count' => $new, /* translators: %s: number of leads */ 'title' => sprintf( _n( '%s new lead to answer', '%s new leads to answer', $new, 'dealroot' ), number_format_i18n( $new ) ), 'text' => $waiting /* translators: %s: number of leads */ ? sprintf( _n( '%s of them has been waiting for more than a day.', '%s of them have been waiting for more than a day.', $waiting, 'dealroot' ), number_format_i18n( $waiting ) ) : __( 'All from the last 24 hours. A quick reply wins more deals.', 'dealroot' ), // Counted over all time, so the list opens without the period chosen for the reports. 'url' => Shell::url( Admin::LEADS_PAGE, array( 'status' => 'new', 'period' => 'all', ) ), 'action' => __( 'Open', 'dealroot' ), ); } $no_value = Leads::query( array( 'status' => 'won', 'no_value' => true, 'per_page' => 1, ) )['total']; if ( $no_value > 0 ) { $items[] = array( 'tone' => 'warning', 'count' => $no_value, /* translators: %s: number of deals */ 'title' => sprintf( _n( '%s won deal without an amount', '%s won deals without an amount', $no_value, 'dealroot' ), number_format_i18n( $no_value ) ), 'text' => __( 'Add the amounts so that revenue and the return on ad spend are right.', 'dealroot' ), 'url' => Shell::url( Admin::LEADS_PAGE, array( 'status' => 'won', 'value' => 'missing', 'period' => 'all', ) ), 'action' => __( 'Add amounts', 'dealroot' ), ); } /** * Filters the list of things that need attention on the overview. * * @param array[] $items Each with tone (alert, warning, info), title, text, url, action and an optional count. */ return (array) apply_filters( 'dealroot_attention_items', $items ); } /** * Things that need attention. */ private function attention() { $items = self::attention_items(); ?>

    4 ) : ?>

  1. 'dealroot-button--small' ) ), Kses::screen() ); ?>
  2. $from, 'to' => $to, ) ); $total = array_sum( $counts ) - $counts['spam']; $labels = Leads::status_labels(); ?>

    0 ) { echo ' ' . esc_html( sprintf( /* translators: %s: number of submissions */ _n( '%s more marked as spam', '%s more marked as spam', $counts['spam'], 'dealroot' ), number_format_i18n( $counts['spam'] ) ) ) . ''; } ?>

    6 ) )['items']; if ( ! $latest ) { return; } $labels = Leads::status_labels(); ?>

    breakdown( $breakdown, $state ); ?>
    ' . esc_html__( 'No leads or contact clicks in this period.', 'dealroot' ) . '

    '; return; } $dimensions = Reports::dimensions(); $max = max( 1, max( wp_list_pluck( $breakdown, 'leads' ) ) ); $has_clicks = (bool) array_filter( $breakdown, static function ( $row ) { return ! empty( $row['clicks']['_total'] ); } ); $has_visits = 'form' !== $state['dimension'] && array_sum( wp_list_pluck( $breakdown, 'visits' ) ) > 0; $has_cost = (bool) array_filter( $breakdown, static function ( $row ) { return array_sum( $row['cost'] ) > 0; } ); $has_profit = null !== Reports::default_margin() && (bool) array_filter( $breakdown, static function ( $row ) { return ! empty( $row['profit'] ); } ); $list = array_merge( Report_State::url_args( false ), array( 'page' => Admin::LEADS_PAGE ) ); $is_source = 'source' === $state['dimension']; $total = count( $breakdown ); $breakdown = array_slice( $breakdown, 0, self::MAX_ROWS, true ); $index = 0; $columns = 5 + ( $has_visits ? 2 : 0 ) + ( $has_profit ? 1 : 0 ) + ( $has_cost ? ( $has_profit ? 4 : 3 ) : 0 ) + ( $has_clicks ? 1 : 0 ); // Campaigns first; visits and leads without one follow, by the channel they came through. $groups = array( 'named' => $breakdown ); if ( 'campaign' === $state['dimension'] ) { $groups = array( 'named' => array(), 'none' => array(), ); foreach ( $breakdown as $key => $row ) { $group = str_starts_with( (string) $key, '~' ) ? 'none' : 'named'; $groups[ $group ][ $key ] = $row; } } ?>
    self::PER_PAGE ? ' data-dealroot-paged="' . esc_attr( (string) self::PER_PAGE ) . '"' : ''; ?>> $rows ) : ?> > $row ) : ?> rawurlencode( (string) $key ) ) ), admin_url( 'admin.php' ) ); $name = esc_html( $label ); if ( 'channel' === $state['dimension'] ) { $name = '' . $name . ''; } if ( $row['leads'] > 0 ) { $name = '' . $name . ''; } ?> = $row['leads'] && $row['visits'] > 0 ) : ?>
    source_cell( (string) $key, $name, $row, $index >= self::PER_PAGE ); ?> 0 ? '' . esc_html( Pages::title( (string) $key ) ) . '' : esc_html( Pages::title( (string) $key ) ); ?> — > 0 ? ' class="dealroot-underline"' : ''; ?>> 0 ? Format::roas( $row['revenue'], $row['cost'] ) : '—' ); ?> 0 ? Format::roas( $row['profit'], $row['cost'] ) : '—' ); ?>

    utm_source=google&utm_medium=cpc&utm_campaign={campaignid}

    ' . esc_html( $note ) . '

    '; } $offer = $is_source && ! Site_Icons::enabled() && current_user_can( 'manage_options' ) && array_filter( array_map( array( Sources::class, 'domain' ), array_map( 'strval', array_keys( $breakdown ) ) ) ); if ( ! $offer && $total <= self::PER_PAGE ) { return; } ?>
    self::MAX_ROWS ) : ?>

    self::PER_PAGE ) : ?>
    >

    $form ) : ?>
    $form ) : ?>

    $count ) : ?>

    = $since ) ) { return ''; } return sprintf( /* translators: %s: date */ __( 'Counted so since %s; the days before are left out, as they missed visitors without consent and forms sent by script.', 'dealroot' ), wp_date( (string) get_option( 'date_format' ), (int) strtotime( $since . ' 12:00:00' ) ) ); } /** * Short notes below the report, e.g. from which day the ad costs count. * * @param array $state Period and report settings. * @return string[] */ public static function notes( array $state ) { /** * Filters the notes below the report on the overview and in the PDF report. * * @param string[] $notes Plain text. * @param array $state Period and report settings. */ return array_filter( array_map( 'strval', (array) apply_filters( 'dealroot_report_notes', array(), $state ) ), 'strlen' ); } /** * Visits of a row. A campaign whose links carry no campaign name has no visits of its own; then the * clicks the ad platform counted stand in, with why. * * @param array $row Row. * @return string HTML. */ private static function visits_cell( array $row ) { if ( $row['visits'] ) { return esc_html( number_format_i18n( $row['visits'] ) ); } $clicks = (int) ( $row['ad_clicks'] ?? 0 ); if ( ! $clicks ) { return '—'; } return sprintf( '%2$s', esc_attr__( 'Clicks counted by the ad platform. The links of this campaign carry no campaign name, so their visits are counted under Without a campaign, by channel. Enquiries from it get the campaign once the ad platform tells which click it was.', 'dealroot' ), /* translators: %s: number of clicks */ esc_html( sprintf( _n( '%s click', '%s clicks', $clicks, 'dealroot' ), number_format_i18n( $clicks ) ) ) ); } }