Making WordPress.org


Ignore:
Timestamp:
03/04/2016 10:34:58 AM (11 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.