__( 'Leads', 'dealroot' ), 'description' => __( 'Enquiries from website forms, where they came from and whether they became customers.', 'dealroot' ), ) ); } /** * Registers the abilities. */ public function register_abilities() { if ( ! function_exists( 'wp_register_ability' ) ) { return; } $meta = array( 'annotations' => array( 'readonly' => true, 'destructive' => false, 'idempotent' => true, ), 'public' => true, 'show_in_rest' => true, 'mcp' => array( 'public' => true ), ); $period = array( 'type' => 'string', 'enum' => array_keys( Reports::periods() ), 'default' => '30d', 'description' => 'Reporting period: 7d, 30d, 90d, this_month, last_month, 12m or all.', ); wp_register_ability( 'dealroot/get-report', array( 'label' => __( 'Get lead report', 'dealroot' ), 'description' => 'Returns visits, leads, won deals, revenue, gross profit (when margins are set), ad spend (when imported) and contact clicks grouped by marketing channel, source, campaign, landing page or form. Use it to answer which channels or campaigns bring enquiries and customers, and which ones pay off.', 'category' => self::CATEGORY, 'input_schema' => array( 'type' => 'object', 'properties' => array( 'period' => $period, 'dimension' => array( 'type' => 'string', 'enum' => array_keys( Reports::dimensions() ), 'default' => 'channel', ), 'model' => array( 'type' => 'string', 'enum' => array( 'first', 'last' ), 'default' => 'first', 'description' => 'Credit the first visit or the visit right before the enquiry.', ), ), 'additionalProperties' => false, ), 'execute_callback' => array( $this, 'get_report' ), 'permission_callback' => array( $this, 'can_view' ), 'meta' => $meta, ) ); wp_register_ability( 'dealroot/list-leads', array( 'label' => __( 'List leads', 'dealroot' ), 'description' => 'Lists recent leads with contact details, status, deal value and marketing source. Filter by status, period or a search term.', 'category' => self::CATEGORY, 'input_schema' => array( 'type' => 'object', 'properties' => array( 'period' => array_merge( $period, array( 'default' => 'all' ) ), 'status' => array( 'type' => 'string', 'enum' => Leads::STATUSES, ), 'search' => array( 'type' => 'string' ), 'limit' => array( 'type' => 'integer', 'minimum' => 1, 'maximum' => 50, 'default' => 20, ), ), 'additionalProperties' => false, ), 'execute_callback' => array( $this, 'list_leads' ), 'permission_callback' => array( $this, 'can_view' ), 'meta' => $meta, ) ); wp_register_ability( 'dealroot/get-lead', array( 'label' => __( 'Get lead', 'dealroot' ), 'description' => 'Returns one lead with all submitted fields, its first and most recent visit, the pages viewed before the enquiry and earlier enquiries from the same person.', 'category' => self::CATEGORY, 'input_schema' => array( 'type' => 'object', 'properties' => array( 'id' => array( 'type' => 'integer', 'minimum' => 1, ), ), 'required' => array( 'id' ), 'additionalProperties' => false, ), 'execute_callback' => array( $this, 'get_lead' ), 'permission_callback' => array( $this, 'can_view' ), 'meta' => $meta, ) ); } /** * Same capability as the Leads screen. * * @return bool */ public function can_view() { return current_user_can( Admin\Admin::capability() ); } /** * Report rows. * * @param array $input Input. * @return array */ public function get_report( $input = array() ) { $input = (array) $input; $period = $input['period'] ?? '30d'; $dimension = $input['dimension'] ?? 'channel'; $model = $input['model'] ?? 'first'; $rows = array(); foreach ( Reports::breakdown( $period, $dimension, $model ) as $key => $row ) { $rows[] = array( 'key' => (string) $key, 'label' => Reports::label( $dimension, (string) $key ), 'visits' => (int) $row['visits'], 'leads' => (int) $row['leads'], 'conversion_rate' => $row['visits'] >= $row['leads'] && $row['visits'] > 0 ? round( $row['leads'] / $row['visits'] * 100, 2 ) : null, 'won' => (int) $row['won'], 'revenue' => $row['revenue'], 'gross_profit' => $row['profit'], 'ad_spend' => $row['cost'], 'contact_clicks' => (int) ( $row['clicks']['_total'] ?? 0 ), ); } return array( 'period' => $period, 'dimension' => $dimension, 'model' => $model, 'median_response_secs' => Reports::response_time( $period ), 'rows' => $rows, ); } /** * Lead list. * * @param array $input Input. * @return array */ public function list_leads( $input = array() ) { $input = (array) $input; $range = Reports::range( $input['period'] ?? 'all' ); $result = Leads::query( array( 'status' => $input['status'] ?? '', 'search' => (string) ( $input['search'] ?? '' ), 'from' => $range[0], 'to' => $range[1], 'per_page' => min( 50, max( 1, (int) ( $input['limit'] ?? 20 ) ) ), ) ); return array( 'total' => $result['total'], 'leads' => array_map( array( $this, 'summary' ), $result['items'] ), ); } /** * Full lead. * * @param array $input Input. * @return array|\WP_Error */ public function get_lead( $input = array() ) { $lead = Leads::get( (int) ( ( (array) $input )['id'] ?? 0 ) ); if ( ! $lead ) { return new \WP_Error( 'dealroot_not_found', 'Lead not found.' ); } return array_merge( $this->summary( $lead ), array( 'fields' => (array) json_decode( (string) $lead->fields, true ), 'note' => $lead->note, 'attribution' => json_decode( (string) $lead->attribution, true ), 'first_response_at' => $lead->contacted_at ? gmdate( 'c', strtotime( $lead->contacted_at . ' UTC' ) ) : null, 'other_enquiries' => array_map( static function ( $other ) { return array( 'id' => (int) $other->id, 'created_at' => gmdate( 'c', strtotime( $other->created_at . ' UTC' ) ), 'status' => $other->status, ); }, Leads::related( $lead ) ), ) ); } /** * Compact lead data. * * @param object $lead Lead row. * @return array */ private function summary( $lead ) { return array( 'id' => (int) $lead->id, 'created_at' => gmdate( 'c', strtotime( $lead->created_at . ' UTC' ) ), 'name' => $lead->name, 'email' => $lead->email, 'phone' => $lead->phone, 'form' => $lead->form_name, 'status' => $lead->status, 'value' => null === $lead->value ? null : (float) $lead->value, 'currency' => $lead->currency, 'channel' => Attribution::channel_label( $lead->channel ), 'source' => $lead->source, 'campaign' => $lead->campaign, 'last_channel' => Attribution::channel_label( $lead->last_channel ), 'admin_url' => admin_url( 'admin.php?page=dealroot&lead=' . (int) $lead->id ), ); } }