# easy-invoice/2.4.4/includes/Import/SproutImporter.php

Easy Invoice – Invoice Generator, PDF Quotes &amp; Payments, version 2.4.4. 245 lines.

- Page: https://pluginprobe.com/plugins/easy-invoice/2.4.4/code/includes/Import/SproutImporter.php
- Raw: https://pluginprobe.com/plugins/easy-invoice/2.4.4/raw/includes/Import/SproutImporter.php
- Modified: 2026-09-15T12:31:20+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/easy-invoice/2.4.4/code/includes/Import/SproutImporter.php#L10-L20`.

```php
<?php
/**
 * Import from Sprout Invoices.
 *
 * @package Easy_Invoice
 * @subpackage Import
 */

namespace EasyInvoice\Import;

if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

/**
 * Sprout stores clients as `sa_client` posts (address as an array in
 * `_address`, users attached through `_associated_users`), invoices as
 * `sa_invoice`, estimates as `sa_estimate` and payments as `sa_payment`
 * posts. Line items are `_doc_line_items`: rows of desc / rate / qty / tax
 * (the per-line adjustment) / total. Dates are Unix timestamps. Status is
 * the post status, with Sprout's own names: temp is a draft, publish is
 * pending, complete is paid.
 *
 * Read from Sprout Invoices 20.x (`models/Invoice.php`, `Estimate.php`,
 * `Client.php`, `Payment.php`).
 */
class SproutImporter extends Importer {

    public function source(): string {
        return 'sprout';
    }

    public function label(): string {
        return __( 'Sprout Invoices', 'easy-invoice' );
    }

    public function available(): bool {
        return $this->count( 'sa_invoice' ) + $this->count( 'sa_estimate' ) > 0;
    }

    public function preview(): array {
        $invoices = $this->ids( 'sa_invoice' );
        $quotes   = $this->ids( 'sa_estimate' );
        $already  = 0;
        foreach ( $invoices as $id ) {
            if ( $this->alreadyImported( \EasyInvoice\Constants\PostTypes::EASY_INVOICE_POST_TYPE, (string) $id ) ) {
                $already++;
            }
        }
        foreach ( $quotes as $id ) {
            if ( $this->alreadyImported( \EasyInvoice\Constants\PostTypes::EASY_INVOICE_QUOTE_POST_TYPE, (string) $id ) ) {
                $already++;
            }
        }
        return [
            'clients'  => $this->count( 'sa_client' ),
            'invoices' => count( $invoices ),
            'quotes'   => count( $quotes ),
            'payments' => $this->count( 'sa_payment' ),
            'already'  => $already,
        ];
    }

    public function run(): array {
        foreach ( $this->ids( 'sa_estimate' ) as $id ) {
            $d                = $this->document( $id, 'estimate' );
            $d['status']      = $this->estimateStatus( get_post_status( $id ) );
            $d['expiry_date'] = $this->toDate( get_post_meta( $id, '_expiration_date', true ) );
            $this->createQuote( $d );
        }

        $invoice_map = [];
        foreach ( $this->ids( 'sa_invoice' ) as $id ) {
            $d             = $this->document( $id, 'invoice' );
            $d['status']   = $this->invoiceStatus( get_post_status( $id ) );
            $d['due_date'] = $this->toDate( get_post_meta( $id, '_due_date', true ) );
            $new           = $this->createInvoice( $d );
            if ( $new > 0 ) {
                $invoice_map[ $id ] = $new;
            }
        }

        foreach ( $this->ids( 'sa_payment', [ 'publish', 'payment-partial', 'refunded', 'void', 'authorized' ] ) as $pid ) {
            $inv = (int) get_post_meta( $pid, '_payment_invoice', true );
            if ( ! isset( $invoice_map[ $inv ] ) ) {
                $mapped = $this->alreadyImported( \EasyInvoice\Constants\PostTypes::EASY_INVOICE_POST_TYPE, (string) $inv );
                if ( $mapped <= 0 ) {
                    continue;
                }
                $invoice_map[ $inv ] = $mapped;
            }
            $status = get_post_status( $pid );
            $this->createPayment( $invoice_map[ $inv ], [
                'source_id'      => (string) $pid,
                'amount'         => $this->toNumber( get_post_meta( $pid, '_amount', true ) ),
                'date'           => get_post_field( 'post_date', $pid ),
                'method'         => (string) get_post_meta( $pid, '_payment_method', true ) ?: 'imported',
                'transaction_id' => (string) get_post_meta( $pid, '_trans_id', true ),
                'status'         => in_array( $status, [ 'publish', 'payment-partial' ], true ) ? 'completed' : ( 'refunded' === $status ? 'refunded' : ( 'void' === $status ? 'failed' : 'pending' ) ),
                'notes'          => (string) get_post_field( 'post_title', $pid ),
            ] );
        }

        return $this->report();
    }

    /**
     * Shared fields, normalised.
     *
     * @param int    $id   Sprout post id.
     * @param string $kind 'invoice' or 'estimate'.
     * @return array
     */
    private function document( int $id, string $kind ): array {
        $post        = get_post( $id );
        $sa_client   = (int) get_post_meta( $id, '_client_id', true );
        $client_id   = $this->client( $sa_client );
        $client_post = $sa_client ? get_post( $sa_client ) : null;

        $items = [];
        foreach ( (array) get_post_meta( $id, '_doc_line_items', true ) as $row ) {
            if ( ! is_array( $row ) ) {
                continue;
            }
            $desc = wp_strip_all_tags( (string) ( $row['desc'] ?? '' ) );
            $items[] = [
                'name'              => strtok( $desc, "\n" ) ?: __( 'Item', 'easy-invoice' ),
                'description'       => trim( (string) substr( $desc, strlen( strtok( $desc, "\n" ) ?: '' ) ) ),
                'quantity'          => $this->toNumber( $row['qty'] ?? 1 ),
                'price'             => $this->toNumber( $row['rate'] ?? 0 ),
                'adjust_percentage' => $this->toNumber( $row['tax'] ?? 0 ),
                'taxable'           => true,
            ];
        }

        $issue = $this->toDate( get_post_meta( $id, '_' . $kind . '_issue_date', true ) ) ?: substr( $post->post_date, 0, 10 );

        return [
            'source_id'        => (string) $id,
            'title'            => (string) $post->post_title,
            'number'           => (string) get_post_meta( $id, '_' . $kind . '_id', true ) ?: (string) $id,
            'issue_date'       => $issue,
            'created'          => $post->post_date,
            'client_id'        => $client_id,
            'customer_name'    => $client_post ? $client_post->post_title : '',
            'customer_email'   => $client_id ? (string) get_userdata( $client_id )->user_email : '',
            'customer_address' => $client_post ? $this->address( (array) get_post_meta( $client_post->ID, '_address', true ) ) : '',
            'items'            => $items,
            'tax_rate'         => $this->toNumber( get_post_meta( $id, '_doc_tax', true ) ),
            'discount_type'    => 'percentage',
            'discount_value'   => $this->toNumber( get_post_meta( $id, '_doc_discount', true ) ),
            'currency_code'    => strtoupper( (string) get_post_meta( $id, '_doc_currency', true ) ),
            'notes'            => wp_strip_all_tags( (string) get_post_meta( $id, '_' . $kind . '_notes', true ) ),
            'terms'            => wp_strip_all_tags( (string) get_post_meta( $id, '_doc_terms', true ) ),
        ];
    }

    /**
     * Map a Sprout client post to ours.
     *
     * @param int $sa_client Sprout client post id.
     * @return int
     */
    private function client( int $sa_client ): int {
        if ( $sa_client <= 0 ) {
            return 0;
        }
        $post = get_post( $sa_client );
        if ( ! $post ) {
            return 0;
        }
        $email = '';
        $first = '';
        $last  = '';
        $users = get_post_meta( $sa_client, '_associated_users', false );
        foreach ( (array) $users as $uid ) {
            $u = get_userdata( (int) $uid );
            if ( $u ) {
                $email = $u->user_email;
                $first = $u->first_name;
                $last  = $u->last_name;
                break;
            }
        }
        if ( '' === $first && '' === $last ) {
            list( $first, $last ) = $this->splitName( $post->post_title );
        }

        return $this->findOrCreateClient( [
            'source_id'  => (string) $sa_client,
            'email'      => $email,
            'first_name' => $first,
            'last_name'  => $last,
            'business'   => (string) $post->post_title,
            'address'    => $this->address( (array) get_post_meta( $sa_client, '_address', true ) ),
            'phone'      => (string) get_post_meta( $sa_client, '_phone', true ),
            'website'    => (string) get_post_meta( $sa_client, '_website', true ),
        ] );
    }

    /**
     * Sprout's address array as lines.
     *
     * @param array $a street, street_2, city, zone, postal_code, country.
     * @return string
     */
    private function address( array $a ): string {
        $lines = array_filter( [
            trim( (string) ( $a['street'] ?? '' ) ),
            trim( (string) ( $a['street_2'] ?? '' ) ),
            trim( implode( ' ', array_filter( [ $a['city'] ?? '', $a['zone'] ?? '', $a['postal_code'] ?? '' ] ) ) ),
            trim( (string) ( $a['country'] ?? '' ) ),
        ] );
        return implode( "\n", $lines );
    }

    private function invoiceStatus( string $status ): string {
        $map = [ 'temp' => 'draft', 'future' => 'draft', 'publish' => 'available', 'partial' => 'available', 'complete' => 'paid', 'write-off' => 'cancelled', 'archived' => 'cancelled' ];
        return $map[ $status ] ?? 'available';
    }

    private function estimateStatus( string $status ): string {
        $map = [ 'temp' => 'draft', 'request' => 'available', 'publish' => 'sent', 'future' => 'draft', 'approved' => 'accepted', 'declined' => 'declined', 'archived' => 'declined' ];
        return $map[ $status ] ?? 'available';
    }

    /**
     * Ids of a Sprout post type.
     *
     * @param string   $type     Post type.
     * @param string[] $statuses Post statuses to include.
     * @return int[]
     */
    private function ids( string $type, array $statuses = [ 'temp', 'publish', 'partial', 'complete', 'write-off', 'archived', 'request', 'approved', 'declined', 'future' ] ): array {
        global $wpdb;
        $in = implode( ',', array_fill( 0, count( $statuses ), '%s' ) );
        return array_map( 'intval', (array) $wpdb->get_col( $wpdb->prepare( "SELECT ID FROM {$wpdb->posts} WHERE post_type = %s AND post_status IN ($in) ORDER BY ID ASC", array_merge( [ $type ], $statuses ) ) ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- placeholders list.
    }

    private function count( string $type ): int {
        global $wpdb;
        return (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->posts} WHERE post_type = %s AND post_status NOT IN ('trash','auto-draft')", $type ) );
    }
}

```
