Making WordPress.org


Ignore:
Timestamp:
03/04/2016 10:34:58 AM (10 years ago)
Author:
kovshenin
Message:

WordCamp Budgets: Move vendor payments exports to a new screet + some refactoring.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-payments-network/includes/wordcamp-budgets-dashboard.php

    r2535 r2694  
    88 */
    99
    10 add_action( 'network_admin_menu',    __NAMESPACE__ . '\register_budgets_menu' );
    11 add_action( 'network_admin_menu',    __NAMESPACE__ . '\remove_budgets_submenu', 11 ); // after other modules have registered their submenu pages
    12 add_action( 'admin_enqueue_scripts', __NAMESPACE__ . '\enqueue_scripts' );
     10add_action( 'network_admin_menu', __NAMESPACE__ . '\register_budgets_menu' );
     11add_action( 'network_admin_menu', __NAMESPACE__ . '\remove_budgets_submenu', 11 ); // after other modules have registered their submenu pages
     12add_action( 'network_admin_menu', __NAMESPACE__ . '\import_export_admin_menu', 11 );
     13add_action( 'admin_enqueue_scripts', __NAMESPACE__ . '\enqueue_scripts', 10, 1 );
     14
     15add_action( 'admin_init', __NAMESPACE__ . '\process_export_request' );
    1316
    1417/**
     
    3235
    3336/**
     37 * Register the Import/Export dashboard menu item.
     38 */
     39function import_export_admin_menu() {
     40    add_submenu_page(
     41        'wordcamp-budgets-dashboard',
     42        'WordCamp Budgets Import/Export',
     43        'Import/Export',
     44        'manage_network',
     45        'wcb-import-export',
     46        __NAMESPACE__ . '\render_import_export'
     47    );
     48}
     49
     50/**
     51 * Render the import/export screen.
     52 */
     53function render_import_export() {
     54    $current_tab = 'import';
     55    if ( ! empty( $_GET['section'] ) && in_array( $_GET['section'], array( 'import', 'export' ) ) ) {
     56        $current_tab = $_GET['section'];
     57    }
     58
     59    ?>
     60        <div class="wrap">
     61            <h1>Import/Export</h1>
     62
     63            <?php do_action( 'admin_notices' ); ?>
     64            <?php settings_errors(); ?>
     65
     66            <h3 class="nav-tab-wrapper">
     67                <a class="nav-tab <?php if ( $current_tab == 'import' ) { echo 'nav-tab-active'; } ?>"
     68                    href="<?php echo add_query_arg( array(
     69                        'page' => 'wcb-import-export',
     70                        'section' => 'import',
     71                    ), network_admin_url( 'admin.php' ) ); ?>">Import</a>
     72
     73                <a class="nav-tab <?php if ( $current_tab == 'export' ) { echo 'nav-tab-active'; } ?>"
     74                    href="<?php echo add_query_arg( array(
     75                        'page' => 'wcb-import-export',
     76                        'section' => 'export',
     77                    ), network_admin_url( 'admin.php' ) ); ?>">Export</a>
     78            </h3>
     79
     80            <?php
     81                if ( 'export' == $current_tab ) {
     82                    render_export_tab();
     83                } elseif ( 'import' == $current_tab ) {
     84                    render_import_tab();
     85                }
     86            ?>
     87
     88        </div> <!-- /wrap -->
     89    <?php
     90}
     91
     92/**
     93 * Get available export options.
     94 *
     95 * @return array
     96 */
     97function get_export_types() {
     98    return array(
     99        'default' => array(
     100            'label' => 'Regular CSV',
     101            'mime_type' => 'text/csv',
     102            'callback' => __NAMESPACE__ . '\_generate_payment_report_default',
     103            'filename' => 'wordcamp-payments-%s-%s-default.csv',
     104        ),
     105        'jpm_wires' => array(
     106            'label' => 'JP Morgan Access - Wire Payments',
     107            'mime_type' => 'text/csv',
     108            'callback' => __NAMESPACE__ . '\_generate_payment_report_jpm_wires',
     109            'filename' => 'wordcamp-payments-%s-%s-jpm-wires.csv',
     110        ),
     111        'jpm_ach' => array(
     112            'label' => 'JP Morgan - NACHA',
     113            'mime_type' => 'text/plain',
     114            'callback' => __NAMESPACE__ . '\_generate_payment_report_jpm_ach',
     115            'filename' => 'wordcamp-payments-%s-%s-jpm-ach.ach',
     116        ),
     117        'jpm_checks' => array(
     118            'label' => 'JP Morgan - Quick Checks',
     119            'mime_type' => 'text/csv',
     120            'callback' => __NAMESPACE__ . '\_generate_payment_report_jpm_checks',
     121            'filename' => 'wordcamp-payments-%s-%s-jpm-checks.csv',
     122        ),
     123    );
     124}
     125
     126/**
     127 * Render the Import tab
     128 */
     129function render_import_tab() {
     130    echo '<p>Move along, nothing to see here.</p>';
     131}
     132
     133/**
     134 * Render the export tab
     135 */
     136function render_export_tab() {
     137        $today = date( 'Y-m-d' );
     138        $last_month = date( 'Y-m-d', strtotime( 'now - 1 month' ) );
     139        ?>
     140        <script>
     141        /**
     142         * Fallback to the jQueryUI datepicker if the browser doesn't support <input type="date">
     143         */
     144        jQuery( document ).ready( function( $ ) {
     145            var browserTest = document.createElement( 'input' );
     146            browserTest.setAttribute( 'type', 'date' );
     147
     148            if ( 'text' === browserTest.type ) {
     149                $( '#wcpn_export' ).find( 'input[type=date]' ).datepicker( {
     150                    dateFormat : 'yy-mm-dd',
     151                    changeMonth: true,
     152                    changeYear : true
     153                } );
     154            }
     155        } );
     156        </script>
     157
     158        <form id="wcpn_export" method="POST">
     159            <?php wp_nonce_field( 'export', 'wcb-request-export' ); ?>
     160
     161            <h2>Export Settings</h2>
     162
     163            <table class="form-table">
     164                <tr>
     165                    <th>Types</th>
     166                    <td>
     167                        <label><input type="checkbox" name="wcb-export-types-vendor-payments"
     168                            value="1" checked disabled /> Vendor Payments</label><br />
     169                        <label><input type="checkbox" name="wcb-export-types-reimbursements"
     170                            value="1" disabled /> Reimbursements</label>
     171                    </td>
     172                <tr>
     173                    <th>Status</th>
     174                    <td>
     175                        <select name="wcb-export-status">
     176                            <option value="wcb-approved"><?php _e( 'Approved', 'wordcamporg' ); ?></option>
     177                            <option value="wcb-paid"><?php _e( 'Paid', 'wordcamporg' ); ?></option>
     178                        </select>
     179                    </td>
     180                </tr>
     181                <tr>
     182                    <th>Date Range</th>
     183                    <td>
     184                        <input type="date" name="wcb-export-start-date"
     185                            class="medium-text" value="<?php echo esc_attr( $last_month ); ?>" /> to
     186                        <input type="date" name="wcb-export-end-date"
     187                            class="medium-text" value="<?php echo esc_attr( $today ); ?>" />
     188                    </td>
     189                </tr>
     190                <tr>
     191                    <th>Format</th>
     192                    <td>
     193                        <select name="wcb-export-type">
     194                            <?php foreach ( get_export_types() as $key => $export_type ) : ?>
     195                            <option value="<?php echo esc_attr( $key ); ?>"><?php echo esc_html( $export_type['label'] ); ?></option>
     196                            <?php endforeach; ?>
     197                        </select>
     198                    </td>
     199                </tr>
     200            </table>
     201
     202            <?php submit_button( 'Download Export' ); ?>
     203        </form>
     204        <?php
     205}
     206
     207/**
     208 * Process export requests
     209 */
     210function process_export_request() {
     211    if ( empty( $_GET['page'] ) || $_GET['page'] != 'wcb-import-export' )
     212        return;
     213
     214    if ( empty( $_GET['section'] ) || $_GET['section'] != 'export' )
     215        return;
     216
     217    if ( empty( $_POST['wcb-request-export'] ) )
     218        return;
     219
     220    if ( ! current_user_can( 'manage_network' ) || ! check_admin_referer( 'export', 'wcb-request-export' ) )
     221        return;
     222
     223    $export_types = get_export_types();
     224
     225    if ( array_key_exists( $_POST['wcb-export-type'], $export_types ) ) {
     226        $export_type = $export_types[ $_POST['wcb-export-type'] ];
     227    } else {
     228        $export_type = $export_types['default'];
     229    }
     230
     231    $status = $_POST['wcb-export-status'];
     232    if ( ! in_array( $status, array( 'wcb-approved', 'wcb-paid' ) ) )
     233        $status = 'wcb-approved';
     234
     235    $start_date = strtotime( $_POST['wcb-export-start-date'] . ' 00:00:00' );
     236    $end_date   = strtotime( $_POST['wcb-export-end-date']   . ' 23:59:59' );
     237    $filename = sprintf( $export_type['filename'], date( 'Ymd', $start_date ), date( 'Ymd', $end_date ) );
     238    $filename = sanitize_file_name( $filename );
     239
     240    $report = generate_payment_report( array(
     241        'status' => $status,
     242        'start_date' => $start_date,
     243        'end_date' => $end_date,
     244        'export_type' => $export_type,
     245    ) );
     246
     247    if ( is_wp_error( $report ) ) {
     248        add_settings_error( 'wcb-dashboard', $report->get_error_code(), $report->get_error_message() );
     249    } else {
     250        header( sprintf( 'Content-Type: %s', $export_type['mime_type'] ) );
     251        header( sprintf( 'Content-Disposition: attachment; filename="%s"', $filename ) );
     252        header( 'Cache-control: private' );
     253        header( 'Pragma: private' );
     254        header( 'Expires: Mon, 26 Jul 1997 05:00:00 GMT' );
     255
     256        echo $report;
     257        die();
     258    }
     259}
     260
     261/*
     262 * Generate and return the raw payment report contents
     263 *
     264 * @param array $args
     265 *
     266 * @return string | WP_Error
     267 */
     268function generate_payment_report( $args ) {
     269    global $wpdb;
     270
     271    $args = wp_parse_args( $args, array(
     272        'status'      => '',
     273        'start_date'  => '',
     274        'end_date'    => '',
     275        'export_type' => '',
     276    ) );
     277
     278    if ( ! is_int( $args['start_date'] ) || ! is_int( $args['end_date'] ) ) {
     279        return new WP_Error( 'wcb-bad-dates', 'Invalid start or end date.' );
     280    }
     281
     282    // todo: support other index tables.
     283    $table_name = $wpdb->get_blog_prefix(0) . 'wordcamp_payments_index';
     284    $date_type = 'updated';
     285
     286    if ( $args['status'] == 'wcb-paid' )
     287        $date_type = 'paid';
     288
     289    $request_indexes = $wpdb->get_results( $wpdb->prepare( "
     290        SELECT *
     291        FROM   `{$table_name}`
     292        WHERE  `{$date_type}` BETWEEN %d AND %d",
     293        $args['start_date'],
     294        $args['end_date']
     295    ) );
     296
     297    if ( ! is_callable( $args['export_type']['callback'] ) )
     298        return new \WP_Error( 'wcb-invalid-type', 'The export type is invalid.' );
     299
     300    $args['request_indexes'] = $request_indexes;
     301
     302    return call_user_func( $args['export_type']['callback'], $args );
     303}
     304
     305/**
     306 * Default CSV report
     307 *
     308 * @param array $args
     309 *
     310 * @return string
     311 */
     312function _generate_payment_report_default( $args ) {
     313    $args = wp_parse_args( $args, array(
     314        'request_indexes' => array(),
     315        'status' => '',
     316    ) );
     317
     318    $column_headings = array(
     319        'WordCamp', 'ID', 'Title', 'Status', 'Date Vendor was Paid', 'Creation Date', 'Due Date', 'Amount',
     320        'Currency', 'Category', 'Payment Method','Vendor Name', 'Vendor Contact Person', 'Vendor Country',
     321        'Check Payable To', 'URL', 'Supporting Documentation Notes',
     322    );
     323
     324    ob_start();
     325    $report = fopen( 'php://output', 'w' );
     326
     327    fputcsv( $report, $column_headings );
     328
     329    foreach( $args['request_indexes'] as $index ) {
     330        switch_to_blog( $index->blog_id );
     331
     332        $request = get_post( $index->post_id );
     333
     334        $back_compat_statuses = array(
     335            'unpaid' => 'draft',
     336            'incomplete' => 'wcb-incomplete',
     337            'paid' => 'wcb-paid',
     338        );
     339
     340        // Map old statuses to new statuses.
     341        if ( array_key_exists( $request->post_status, $back_compat_statuses ) ) {
     342            $request->post_status = $back_compat_statuses[ $request->post_status ];
     343        }
     344
     345        if ( $args['status'] && $request->post_status != $args['status'] ) {
     346            restore_current_blog();
     347            continue;
     348        }
     349
     350        $currency = get_post_meta( $index->post_id, '_camppayments_currency', true );
     351        $category = get_post_meta( $index->post_id, '_camppayments_payment_category', true );
     352        $date_vendor_paid = get_post_meta( $index->post_id, '_camppayments_date_vendor_paid', true );
     353
     354        if ( $date_vendor_paid ) {
     355            $date_vendor_paid = date( 'Y-m-d', $date_vendor_paid );
     356        }
     357
     358        if ( 'null-select-one' === $currency ) {
     359            $currency = '';
     360        }
     361
     362        if ( 'null' === $category ) {
     363            $category = '';
     364        }
     365
     366        $row = array(
     367            get_wordcamp_name(),
     368            sprintf( '%d-%d', $index->blog_id, $index->post_id ),
     369            html_entity_decode( $request->post_title ),
     370            $index->status,
     371            $date_vendor_paid,
     372            date( 'Y-m-d', $index->created ),
     373            date( 'Y-m-d', $index->due ),
     374            get_post_meta( $index->post_id, '_camppayments_payment_amount', true ),
     375            $currency,
     376            $category,
     377            get_post_meta( $index->post_id, '_camppayments_payment_method', true ),
     378            get_post_meta( $index->post_id, '_camppayments_vendor_name', true ),
     379            get_post_meta( $index->post_id, '_camppayments_vendor_contact_person', true ),
     380            get_post_meta( $index->post_id, '_camppayments_vendor_country', true ),
     381            \WCP_Encryption::maybe_decrypt( get_post_meta( $index->post_id, '_camppayments_payable_to', true ) ),
     382            get_edit_post_link( $index->post_id ),
     383            get_post_meta( $index->post_id, '_camppayments_file_notes', true ),
     384        );
     385
     386        restore_current_blog();
     387
     388        if ( ! empty( $row ) ) {
     389            fputcsv( $report, $row );
     390        }
     391    }
     392
     393    fclose( $report );
     394    return ob_get_clean();
     395}
     396
     397/**
     398 * Quick Checks via JP Morgan
     399 *
     400 * @param array $args
     401 *
     402 * @return string
     403 */
     404function _generate_payment_report_jpm_checks( $args ) {
     405    $args = wp_parse_args( $args, array(
     406        'request_indexes' => array(),
     407        'status' => '',
     408    ) );
     409
     410    $options = apply_filters( 'wcb_payment_req_check_options', array(
     411        'pws_customer_id' => '',
     412        'account_number'  => '',
     413        'contact_email'   => '',
     414        'contact_phone'   => '',
     415    ) );
     416
     417    $report = fopen( 'php://output', 'w' );
     418    ob_start();
     419
     420    // File Header
     421    fputcsv( $report, array( 'FILHDR', 'PWS', $options['pws_customer_id'], date( 'm/d/Y' ), date( 'Hi' ) ), ',', '|' );
     422
     423    $total = 0;
     424    $count = 0;
     425
     426    if ( false !== get_site_transient( '_wcb_jpm_checks_counter_lock' ) ) {
     427        wp_die( 'JPM Checks Export is locked. Please try again later or contact support.' );
     428    }
     429
     430    // Avoid at least *some* race conditions.
     431    set_site_transient( '_wcb_jpm_checks_counter_lock', 1, 30 );
     432    $start = absint( get_site_option( '_wcb_jpm_checks_counter', 0 ) );
     433
     434    foreach ( $args['request_indexes'] as $index ) {
     435        switch_to_blog( $index->blog_id );
     436        $post = get_post( $index->post_id );
     437
     438        if ( $args['status'] && $post->post_status != $args['status'] ) {
     439            restore_current_blog();
     440            continue;
     441        }
     442
     443        if ( get_post_meta( $post->ID, '_camppayments_payment_method', true ) != 'Check' ) {
     444            restore_current_blog();
     445            continue;
     446        }
     447
     448        $count++;
     449        $amount = round( floatval( get_post_meta( $post->ID, '_camppayments_payment_amount', true ) ), 2 );
     450        $total += $amount;
     451
     452        $payable_to = \WCP_Encryption::maybe_decrypt( get_post_meta( $post->ID, '_camppayments_payable_to', true ) );
     453        $payable_to = html_entity_decode( $payable_to ); // J&amp;J to J&J
     454        $countries = \WordCamp_Budgets::get_valid_countries_iso3166();
     455        $vendor_country_code = get_post_meta( $post->ID, '_camppayments_vendor_country_iso3166', true );
     456        if ( ! empty( $countries[ $vendor_country_code ] ) ) {
     457            $vendor_country_code = $countries[ $vendor_country_code ]['alpha3'];
     458        }
     459
     460        $description = sanitize_text_field( get_post_meta( $post->ID, '_camppayments_description', true ) );
     461        $description = html_entity_decode( $description );
     462        $invoice_number = get_post_meta( $post->ID, '_camppayments_invoice_number', true );
     463        if ( ! empty( $invoice_number ) ) {
     464            $description = sprintf( 'Invoice %s. %s', $invoice_number, $description );
     465        }
     466
     467        // Payment Header
     468        fputcsv( $report, array(
     469            'PMTHDR',
     470            'USPS',
     471            'QKCHECKS',
     472            date( 'm/d/Y' ),
     473            number_format( $amount, 2, '.', '' ),
     474            $options['account_number'],
     475            $start + $count, // must be globally unique?
     476            $options['contact_email'],
     477            $options['contact_phone'],
     478        ), ',', '|' );
     479
     480        // Payee Name Record
     481        fputcsv( $report, array(
     482            'PAYENM',
     483            substr( $payable_to, 0, 35 ),
     484            '',
     485            sprintf( '%d-%d', $index->blog_id, $index->post_id ),
     486        ), ',', '|' );
     487
     488        // Payee Address Record
     489        fputcsv( $report, array(
     490            'PYEADD',
     491            substr( get_post_meta( $post->ID, '_camppayments_vendor_street_address', true ), 0, 35 ),
     492            '',
     493        ), ',', '|' );
     494
     495        // Additional Payee Address Record
     496        fputcsv( $report, array( 'ADDPYE', '', '' ), ',', '|' );
     497
     498        // Payee Postal Record
     499        fputcsv( $report, array(
     500            'PYEPOS',
     501            substr( get_post_meta( $post->ID, '_camppayments_vendor_city', true ), 0, 35 ),
     502            substr( get_post_meta( $post->ID, '_camppayments_vendor_state', true ), 0, 35 ),
     503            substr( get_post_meta( $post->ID, '_camppayments_vendor_zip_code', true ), 0, 10 ),
     504            substr( $vendor_country_code, 0, 3 ),
     505        ), ',', '|' );
     506
     507        // Payment Description
     508        fputcsv( $report, array(
     509            'PYTDES',
     510            substr( $description, 0, 122 ),
     511        ), ',', '|' );
     512
     513        restore_current_blog();
     514    }
     515
     516    // File Trailer
     517    fputcsv( $report, array( 'FILTRL', $count * 6 + 2 ), ',', '|' );
     518
     519    // Update counter and unlock
     520    $start = absint( get_site_option( '_wcb_jpm_checks_counter', 0 ) );
     521    update_site_option( '_wcb_jpm_checks_counter', $start + $count );
     522    delete_site_transient( '_wcb_jpm_checks_counter_lock' );
     523
     524    fclose( $report );
     525    return ob_get_clean();
     526}
     527
     528/**
     529 * NACHA via JP Morgan
     530 *
     531 * @param array $args
     532 *
     533 * @return string
     534 */
     535function _generate_payment_report_jpm_ach( $args ) {
     536    $args = wp_parse_args( $args, array(
     537        'request_indexes' => array(),
     538        'status' => '',
     539    ) );
     540
     541    $ach_options = apply_filters( 'wcb_payment_req_ach_options', array(
     542        'bank-routing-number' => '', // Immediate Destination (bank routing number)
     543        'company-id'          => '', // Company ID
     544        'financial-inst'      => '', // Originating Financial Institution
     545    ) );
     546
     547    ob_start();
     548
     549    // File Header Record
     550
     551    echo '1'; // Record Type Code
     552    echo '01'; // Priority Code
     553    echo ' ' . str_pad( substr( $ach_options['bank-routing-number'], 0, 9 ), 9, '0', STR_PAD_LEFT );
     554    echo str_pad( substr( $ach_options['company-id'], 0, 10 ), 10, '0', STR_PAD_LEFT ); // Immediate Origin (TIN)
     555    echo date( 'ymd' ); // Transmission Date
     556    echo date( 'Hi' ); // Transmission Time
     557    echo 'A'; // File ID Modifier
     558    echo '094'; // Record Size
     559    echo '10'; // Blocking Factor
     560    echo '1'; // Format Code
     561    echo str_pad( 'JPMORGANCHASE', 23 ); // Destination
     562    echo str_pad( 'WCEXPORT', 23 ); // Origin
     563    echo str_pad( '', 8 ); // Reference Code (optional)
     564    echo PHP_EOL;
     565
     566    // Batch Header Record
     567
     568    echo '5'; // Record Type Code
     569    echo '200'; // Service Type Code
     570    echo 'WordCamp Communi'; // Company Name
     571    echo str_pad( '', 20 ); // Blanks
     572    echo str_pad( substr( $ach_options['company-id'], 0, 10 ), 10 ); // Company Identification
     573
     574    // Get the first one in the set.
     575    // @todo Split batches by account type.
     576    foreach ( $args['request_indexes'] as $index ) {
     577        switch_to_blog( $index->blog_id );
     578        $post = get_post( $index->post_id );
     579        $account_type = get_post_meta( $post->ID, '_camppayments_ach_account_type', true );
     580        restore_current_blog();
     581
     582        break;
     583    }
     584
     585    $entry_class = $account_type == 'Personal' ? 'PPD' : 'CCD';
     586    echo $entry_class; // Standard Entry Class
     587
     588    echo 'Vendor Pay'; // Entry Description
     589    echo date( 'ymd', _next_business_day_timestamp() ); // Company Description Date
     590    echo date( 'ymd', _next_business_day_timestamp() ); // Effective Entry Date
     591    echo str_pad( '', 3 ); // Blanks
     592    echo '1'; // Originator Status Code
     593    echo str_pad( substr( $ach_options['financial-inst'], 0, 8 ), 8 ); // Originating Financial Institution
     594    echo '0000001'; // Batch Number
     595    echo PHP_EOL;
     596
     597    $count = 0;
     598    $total = 0;
     599    $hash = 0;
     600
     601    foreach ( $args['request_indexes'] as $index ) {
     602        switch_to_blog( $index->blog_id );
     603        $post = get_post( $index->post_id );
     604
     605        if ( $args['status'] && $post->post_status != $args['status'] ) {
     606            restore_current_blog();
     607            continue;
     608        }
     609
     610        if ( get_post_meta( $post->ID, '_camppayments_payment_method', true ) != 'Direct Deposit' ) {
     611            restore_current_blog();
     612            continue;
     613        }
     614
     615        $count++;
     616
     617        // Entry Detail Record
     618
     619        echo '6'; // Record Type Code
     620
     621        $transaction_code = $account_type == 'Personal' ? '27' : '22';
     622        echo $transaction_code; // Transaction Code
     623
     624        // Transit/Routing Number of Destination Bank + Check digit
     625        $routing_number = get_post_meta( $post->ID, '_camppayments_ach_routing_number', true );
     626        $routing_number = \WCP_Encryption::maybe_decrypt( $routing_number );
     627        $routing_number = substr( $routing_number, 0, 8 + 1 );
     628        $routing_number = str_pad( $routing_number, 8 + 1 );
     629        $hash += absint( substr( $routing_number, 0, 8 ) );
     630        echo $routing_number;
     631
     632        // Bank Account Number
     633        $account_number = get_post_meta( $post->ID, '_camppayments_ach_account_number', true );
     634        $account_number = \WCP_Encryption::maybe_decrypt( $account_number );
     635        $account_number = substr( $account_number, 0, 17 );
     636        $account_number = str_pad( $account_number, 17 );
     637        echo $account_number;
     638
     639        // Amount
     640        $amount = round( floatval( get_post_meta( $post->ID, '_camppayments_payment_amount', true ) ), 2 );
     641        $total += $amount;
     642        $amount = str_pad( number_format( $amount, 2, '', '' ), 10, '0', STR_PAD_LEFT );
     643        echo $amount;
     644
     645        // Individual Identification Number
     646        echo str_pad( sprintf( '%d-%d', $index->blog_id, $index->post_id ), 15 );
     647
     648        // Individual Name
     649        $name = get_post_meta( $post->ID, '_camppayments_ach_account_holder_name', true );
     650        $name = \WCP_Encryption::maybe_decrypt( $name );
     651        $name = substr( $name, 0, 22 );
     652        $name = str_pad( $name, 22 );
     653        echo $name;
     654
     655        echo '  '; // User Defined Data
     656        echo '0'; // Addenda Record Indicator
     657
     658        // Trace Number
     659        echo str_pad( substr( $ach_options['bank-routing-number'], 0, 8 ), 8, '0', STR_PAD_LEFT ); // routing number
     660        echo str_pad( $count, 7, '0', STR_PAD_LEFT ); // sequence number
     661        echo PHP_EOL;
     662    }
     663
     664    // Batch Trailer Record
     665
     666    echo '8'; // Record Type Code
     667    echo '200'; // Service Class Code
     668    echo str_pad( $count, 6, '0', STR_PAD_LEFT ); // Entry/Addenda Count
     669    echo str_pad( substr( $hash, -10 ), 10, '0', STR_PAD_LEFT ); // Entry Hash
     670    echo str_pad( number_format( $total, 2, '', '' ), 12, '0', STR_PAD_LEFT ); // Total Debit Entry Dollar Amount
     671    echo str_pad( 0, 12, '0', STR_PAD_LEFT ); // Total Credit Entry Dollar Amount
     672    echo str_pad( substr( $ach_options['company-id'], 0, 10 ), 10 ); // Company ID
     673    echo str_pad( '', 25 ); // Blanks
     674    echo str_pad( substr( $ach_options['financial-inst'], 0, 8 ), 8 ); // Originating Financial Institution
     675    echo '0000001'; // Batch Number
     676    echo PHP_EOL;
     677
     678
     679    // File Trailer Record
     680
     681    echo '9'; // Record Type Code
     682    echo '000001'; // Batch Count
     683    echo str_pad( ceil( $count / 10 ), 6, '0', STR_PAD_LEFT ); // Block Count
     684    echo str_pad( $count, 8, '0', STR_PAD_LEFT ); // Entry/Addenda Count
     685    echo str_pad( substr( $hash, -10 ), 10, '0', STR_PAD_LEFT ); // Entry Hash
     686    echo str_pad( number_format( $total, 2, '', '' ), 12, '0', STR_PAD_LEFT ); // Total Debit Entry Dollar Amount
     687    echo str_pad( 0, 12, '0', STR_PAD_LEFT ); // Total Credit Entry Dollar Amount
     688    echo str_pad( '', 39 ); // Blanks
     689    echo PHP_EOL;
     690
     691    // The file must have a number of lines that is a multiple of 10 (e.g. 10, 20, 30).
     692    echo str_repeat( PHP_EOL, 10 - ( ( 4 + $count ) % 10 ) - 1 );
     693    return ob_get_clean();
     694}
     695
     696/**
     697 * Wires via JP Morgan
     698 *
     699 * @param array $args
     700 *
     701 * @return string
     702 */
     703function _generate_payment_report_jpm_wires( $args ) {
     704    $args = wp_parse_args( $args, array(
     705        'request_indexes' => array(),
     706        'status' => '',
     707    ) );
     708
     709    ob_start();
     710    $report = fopen( 'php://output', 'w' );
     711
     712    // JPM Header
     713    fputcsv( $report, array( 'HEADER', gmdate( 'YmdHis' ), '1' ) );
     714
     715    $total = 0;
     716    $count = 0;
     717
     718    foreach ( $args['request_indexes'] as $index ) {
     719        switch_to_blog( $index->blog_id );
     720        $post = get_post( $index->post_id );
     721
     722        if ( $args['status'] && $post->post_status != $args['status'] ) {
     723            restore_current_blog();
     724            continue;
     725        }
     726
     727        // Only wires here.
     728        if ( get_post_meta( $post->ID, '_camppayments_payment_method', true ) != 'Wire' ) {
     729            restore_current_blog();
     730            continue;
     731        }
     732
     733        $amount = round( floatval( get_post_meta( $post->ID, '_camppayments_payment_amount', true ) ), 2);
     734        $total += $amount;
     735        $count += 1;
     736
     737        // If account starts with two letters, it's most likely an IBAN
     738        $account = get_post_meta( $post->ID, '_camppayments_beneficiary_account_number', true );
     739        $account = \WCP_Encryption::maybe_decrypt( $account );
     740        $account = preg_replace( '#\s#','', $account );
     741        $account_type = preg_match( '#^[a-z]{2}#i', $account ) ? 'IBAN' : 'ACCT';
     742
     743        $row = array(
     744            '1-input-type' => 'P',
     745            '2-payment-method' => 'WIRES',
     746            '3-debit-bank-id' => apply_filters( 'wcb_payment_req_bank_id', '' ), // external file
     747            '4-account-number' => apply_filters( 'wcb_payment_req_bank_number', '' ), // external file
     748            '5-bank-to-bank' => 'N',
     749            '6-txn-currency' => get_post_meta( $post->ID, '_camppayments_currency', true ),
     750            '7-txn-amount' => number_format( $amount, 2, '.', '' ),
     751            '8-equiv-amount' => '',
     752            '9-clearing' => '',
     753            '10-ben-residence' => '',
     754            '11-rate-type' => '',
     755            '12-blank' => '',
     756            '13-value-date' => '',
     757
     758            '14-id-type' => $account_type,
     759            '15-id-value' => $account,
     760            '16-ben-name' => substr( \WCP_Encryption::maybe_decrypt(
     761                get_post_meta( $post->ID, '_camppayments_beneficiary_name', true ) ), 0, 35 ),
     762            '17-address-1' => substr( \WCP_Encryption::maybe_decrypt(
     763                get_post_meta( $post->ID, '_camppayments_beneficiary_street_address', true ) ), 0, 35 ),
     764            '18-address-2' => '',
     765            '19-city-state-zip' => substr( sprintf( '%s %s %s',
     766                    \WCP_Encryption::maybe_decrypt( get_post_meta( $post->ID, '_camppayments_beneficiary_city', true ) ),
     767                    \WCP_Encryption::maybe_decrypt( get_post_meta( $post->ID, '_camppayments_beneficiary_state', true ) ),
     768                    \WCP_Encryption::maybe_decrypt( get_post_meta( $post->ID, '_camppayments_beneficiary_zip_code', true ) )
     769                ), 0, 32 ),
     770            '20-blank' => '',
     771            '21-country' => \WCP_Encryption::maybe_decrypt(
     772                get_post_meta( $post->ID, '_camppayments_beneficiary_country_iso3166', true ) ),
     773            '22-blank' => '',
     774            '23-blank' => '',
     775
     776            '24-id-type' => 'SWIFT',
     777            '25-id-value' => get_post_meta( $post->ID, '_camppayments_bank_bic', true ),
     778            '26-ben-bank-name' => substr( get_post_meta( $post->ID, '_camppayments_bank_name', true ), 0, 35 ),
     779            '27-ben-bank-address-1' => substr( get_post_meta( $post->ID, '_camppayments_bank_street_address', true ), 0, 35 ),
     780            '28-ben-bank-address-2' => '',
     781            '29-ben-bank-address-3' => substr( sprintf( '%s %s %s',
     782                    get_post_meta( $post->ID, '_camppayments_bank_city', true ),
     783                    get_post_meta( $post->ID, '_camppayments_bank_state', true ),
     784                    get_post_meta( $post->ID, '_camppayments_bank_zip_code', true )
     785                 ), 0, 35 ),
     786            '30-ben-bank-country' => get_post_meta( $post->ID, '_camppayments_bank_country_iso3166', true ),
     787            '31-supl-id-type' => '',
     788            '32-supl-id-value' => '',
     789
     790            '33-blank' => '',
     791            '34-blank' => '',
     792            '35-blank' => '',
     793            '36-blank' => '',
     794            '37-blank' => '',
     795            '38-blank' => '',
     796            '39-blank' => '',
     797
     798            // Filled out later if not empty.
     799            '40-id-type' => '',
     800            '41-id-value' => '',
     801            '42-interm-bank-name' => '',
     802            '43-interm-bank-address-1' => '',
     803            '44-interm-bank-address-2' => '',
     804            '45-interm-bank-address-3' => '',
     805            '46-interm-bank-country' => '',
     806            '47-supl-id-type' => '',
     807            '48-supl-id-value' => '',
     808
     809            '49-id-type' => '',
     810            '50-id-value' => '',
     811            '51-party-name' => '',
     812            '52-party-address-1' => '',
     813            '53-party-address-2' => '',
     814            '54-party-address-3' => '',
     815            '55-party-country' => '',
     816
     817            '56-blank' => '',
     818            '57-blank' => '',
     819            '58-blank' => '',
     820            '59-blank' => '',
     821            '60-blank' => '',
     822            '61-blank' => '',
     823            '62-blank' => '',
     824            '63-blank' => '',
     825            '64-blank' => '',
     826            '65-blank' => '',
     827            '66-blank' => '',
     828            '67-blank' => '',
     829            '68-blank' => '',
     830            '69-blank' => '',
     831            '70-blank' => '',
     832            '71-blank' => '',
     833            '72-blank' => '',
     834            '73-blank' => '',
     835
     836            '74-ref-text' => substr( get_post_meta( $post->ID, '_camppayments_invoice_number', true ), 0, 16 ),
     837            '75-internal-ref' => '',
     838            '76-on-behalf-of' => '',
     839
     840            '77-detial-1' => '',
     841            '78-detial-2' => '',
     842            '79-detial-3' => '',
     843            '80-detail-4' => '',
     844
     845            '81-blank' => '',
     846            '82-blank' => '',
     847            '83-blank' => '',
     848            '84-blank' => '',
     849            '85-blank' => '',
     850            '86-blank' => '',
     851            '87-blank' => '',
     852            '88-blank' => '',
     853
     854            '89-reporting-code' => '',
     855            '90-country' => '',
     856            '91-inst-1' => '',
     857            '92-inst-2' => '',
     858            '93-inst-3' => '',
     859            '94-inst-code-1' => '',
     860            '95-inst-text-1' => '',
     861            '96-inst-code-2' => '',
     862            '97-inst-text-2' => '',
     863            '98-inst-code-3' => '',
     864            '99-inst-text-3' => '',
     865
     866            '100-stor-code-1' => '',
     867            '101-stor-line-2' => '', // Hmm?
     868            '102-stor-code-2' => '',
     869            '103-stor-line-2' => '',
     870            '104-stor-code-3' => '',
     871            '105-stor-line-3' => '',
     872            '106-stor-code-4' => '',
     873            '107-stor-line-4' => '',
     874            '108-stor-code-5' => '',
     875            '109-stor-line-5' => '',
     876            '110-stor-code-6' => '',
     877            '111-stor-line-6' => '',
     878
     879            '112-priority' => '',
     880            '113-blank' => '',
     881            '114-charges' => '',
     882            '115-blank' => '',
     883            '116-details' => '',
     884            '117-note' => substr( sprintf( 'wcb-%d-%d', $index->blog_id, $index->post_id ), 0, 70 ),
     885        );
     886
     887        // If an intermediary bank is given.
     888        $interm_swift = get_post_meta( $post->ID, '_camppayments_interm_bank_swift', true );
     889        if ( ! empty( $iterm_swift ) ) {
     890            $row['40-id-type'] = 'SWIFT';
     891            $row['41-id-value'] = $interm_swift;
     892
     893            $row['42-interm-bank-name'] = substr( get_post_meta( $post->ID, '_camppayments_interm_bank_name', true ), 0, 35 );
     894            $row['43-interm-bank-address-1'] = substr( get_post_meta( $post->ID, '_camppayments_interm_bank_street_address', true ), 0, 35 );
     895
     896            $row['44-interm-bank-address-2'] = '';
     897            $row['45-interm-bank-address-3'] = substr( sprintf( '%s %s %s',
     898                get_post_meta( $post->ID, '_camppayments_interm_bank_city', true ),
     899                get_post_meta( $post->ID, '_camppayments_interm_bank_state', true ),
     900                get_post_meta( $post->ID, '_camppayments_interm_bank_zip_code', true )
     901            ), 0, 32 );
     902
     903            $row['46-interm-bank-country'] = get_post_meta( $post->ID, '_camppayments_interm_bank_country_iso3166', true );
     904
     905            $row['47-supl-id-type'] = 'ACCT';
     906            $row['48-supl-id-value'] = get_post_meta( $post->ID, '_camppayments_interm_bank_account', true );
     907        }
     908
     909        // Use for debugging.
     910        // print_r( $row );
     911
     912        fputcsv( $report, array_values( $row ) );
     913        restore_current_blog();
     914    }
     915
     916    // JPM Trailer
     917    fputcsv( $report, array( 'TRAILER', $count, $total ) );
     918
     919    fclose( $report );
     920    $results = ob_get_clean();
     921
     922    // JPM chokes on accents and non-latin characters.
     923    $results = remove_accents( $results );
     924    return $results;
     925}
     926
     927/**
     928 * Exclude weekends and JPM holidays.
     929 *
     930 * Needs to be updated every year.
     931 *
     932 * @return int Timestamp.
     933 */
     934function _next_business_day_timestamp() {
     935    static $timestamp;
     936
     937    if ( isset( $timestamp ) )
     938        return $timestamp;
     939
     940    $holidays = array(
     941        date( 'Ymd', strtotime( 'Friday, January 1, 2016' ) ),
     942        date( 'Ymd', strtotime( 'Monday, January 18, 2016' ) ),
     943        date( 'Ymd', strtotime( 'Monday, February 15, 2016' ) ),
     944        date( 'Ymd', strtotime( 'Monday, May 30, 2016' ) ),
     945        date( 'Ymd', strtotime( 'Monday, July 4, 2016' ) ),
     946        date( 'Ymd', strtotime( 'Monday, September 5, 2016' ) ),
     947        date( 'Ymd', strtotime( 'Friday, November 11, 2016' ) ),
     948        date( 'Ymd', strtotime( 'Thursday, November 24, 2016' ) ),
     949        date( 'Ymd', strtotime( 'Monday, December 26, 2016' ) ),
     950    );
     951
     952    $timestamp = strtotime( 'today + 1 weekday' );
     953    $attempts = 5;
     954
     955    while ( in_array( date( 'Ymd', $timestamp ), $holidays ) ) {
     956        $timestamp = strtotime( '+ 1 weekday', $timestamp );
     957        $attempts--;
     958
     959        if ( ! $attempts )
     960            break;
     961    }
     962
     963    return $timestamp;
     964}
     965
     966/**
    34967 * Remove the empty Budgets submenu item
    35968 *
     
    37970 */
    38971function remove_budgets_submenu() {
    39     remove_submenu_page( 'wordcamp-budgets-dashboard', 'wordcamp-budgets-dashboard' );
     972    remove_submenu_page( 'wordcamp-budgets-dashboard', 'wordcamp-budgets-dashboard' );
    40973}
    41974
     
    43976 * Enqueue scripts and styles
    44977 */
    45 function enqueue_scripts() {
     978function enqueue_scripts( $hook ) {
    46979    wp_enqueue_style(
    47980        'wordcamp-budgets-dashboard',
     
    50983        1
    51984    );
     985
     986    if ( $hook == 'budgets_page_wcb-import-export' ) {
     987        wp_enqueue_script( 'jquery-ui-datepicker' );
     988        wp_enqueue_style( 'jquery-ui' );
     989        wp_enqueue_style( 'wp-datepicker-skins' );
     990    }
    52991}
    53992
     
    621001function format_amount( $amount, $currency ) {
    631002    $formatted_amount = '';
    64     $amount           = \WordCamp_Budgets::validate_amount( $amount );
     1003    $amount = \WordCamp_Budgets::validate_amount( $amount );
    651004
    661005    if ( false === strpos( $currency, 'null' ) && $amount ) {
     
    941033    global $wpdb;
    951034
    96     $from      = strtolower( $from );
    97     $to        = strtolower( $to );
     1035    $from = strtolower( $from );
     1036    $to = strtolower( $to );
    981037    $cache_key = md5( sprintf( 'wcp-exchange-rate-%s:%s', $from, $to ) );
    991038
     
    1071046
    1081047        $request = wp_remote_get( esc_url_raw( $url ) );
    109         $body    = json_decode( wp_remote_retrieve_body( $request ), true );
     1048        $body = json_decode( wp_remote_retrieve_body( $request ), true );
    1101049
    1111050        if ( ! empty( $body['query']['results']['rate']['Ask'] ) ) {
Note: See TracChangeset for help on using the changeset viewer.