<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[2694] sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-payments-network/includes: WordCamp Budgets: Move vendor payments exports to a new screet + some refactoring.</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta" style="font-size: 105%">
<dt style="float: left; width: 6em; font-weight: bold">Revision</dt> <dd><a style="font-weight: bold" href="http://meta.trac.wordpress.org/changeset/2694">2694</a><script type="application/ld+json">{"@context":"http://schema.org","@type":"EmailMessage","description":"Review this Commit","action":{"@type":"ViewAction","url":"http://meta.trac.wordpress.org/changeset/2694","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>kovshenin</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2016-03-04 10:34:58 +0000 (Fri, 04 Mar 2016)</dd>
</dl>

<pre style='padding-left: 1em; margin: 2em 0; border-left: 2px solid #ccc; line-height: 1.25; font-size: 105%; font-family: sans-serif'>WordCamp Budgets: Move vendor payments exports to a new screet + some refactoring.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcamppaymentsnetworkincludespaymentrequestsdashboardphp">sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-payments-network/includes/payment-requests-dashboard.php</a></li>
<li><a href="#sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcamppaymentsnetworkincludeswordcampbudgetsdashboardphp">sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-payments-network/includes/wordcamp-budgets-dashboard.php</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcamppaymentsnetworkincludespaymentrequestsdashboardphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-payments-network/includes/payment-requests-dashboard.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-payments-network/includes/payment-requests-dashboard.php 2016-03-03 23:05:06 UTC (rev 2693)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-payments-network/includes/payment-requests-dashboard.php   2016-03-04 10:34:58 UTC (rev 2694)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -16,10 +16,8 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        wp_schedule_event( time(), 'hourly', 'wordcamp_payments_aggregate' );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                add_action( 'wordcamp_payments_aggregate', array( __CLASS__, 'aggregate' ) );
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                add_action( 'admin_enqueue_scripts',  array( __CLASS__, 'enqueue_assets' ) );
</del><span class="cx" style="display: block; padding: 0 10px">                 add_action( 'network_admin_menu', array( __CLASS__, 'network_admin_menu' ) );
</span><span class="cx" style="display: block; padding: 0 10px">                add_action( 'init', array( __CLASS__, 'upgrade' ) );
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                add_action( 'init', array( __CLASS__, 'process_export_request' ) );
</del><span class="cx" style="display: block; padding: 0 10px">                 add_action( 'init', array( __CLASS__, 'process_import_request' ) );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                // Dashboard actions.
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -238,19 +236,6 @@
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         * Enqueue scripts and stylesheets
-        *
-        * @param string $hook
-        */
-       public static function enqueue_assets( $hook ) {
-               if ( 'index_page_wcp-dashboard' == $hook && 'export' == self::get_current_tab() ) {
-                       wp_enqueue_script( 'jquery-ui-datepicker' );
-                       wp_enqueue_style( 'jquery-ui' );
-                       wp_enqueue_style( 'wp-datepicker-skins' );
-               }
-       }
-
-       /**
</del><span class="cx" style="display: block; padding: 0 10px">          * Renders the Dashboard - Payments screen.
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="cx" style="display: block; padding: 0 10px">        public static function render_dashboard() {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -265,9 +250,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        <h3 class="nav-tab-wrapper"><?php self::render_dashboard_tabs(); ?></h3>
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                        <?php
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                if ( 'export' == self::get_current_tab() ) {
-                                       self::render_export_tab();
-                               } elseif ( 'import' == self::get_current_tab() ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         if ( 'import' == self::get_current_tab() ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                                         self::render_import_tab();
</span><span class="cx" style="display: block; padding: 0 10px">                                }
</span><span class="cx" style="display: block; padding: 0 10px">                                else {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -383,857 +366,6 @@
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         * Get available export options.
-        *
-        * @return array
-        */
-       public static function get_export_types() {
-               return array(
-                       'default' => array(
-                               'label' => 'Default',
-                               'mime_type' => 'text/csv',
-                               'callback' => array( __CLASS__, '_generate_payment_report_default' ),
-                               'filename' => 'wordcamp-payments-%s-%s-default.csv',
-                       ),
-                       'jpm_wires' => array(
-                               'label' => 'JP Morgan Access - Wire Payments',
-                               'mime_type' => 'text/csv',
-                               'callback' => array( __CLASS__, '_generate_payment_report_jpm_wires' ),
-                               'filename' => 'wordcamp-payments-%s-%s-jpm-wires.csv',
-                       ),
-                       'jpm_ach' => array(
-                               'label' => 'JP Morgan - NACHA',
-                               'mime_type' => 'text/plain',
-                               'callback' => array( __CLASS__, '_generate_payment_report_jpm_ach' ),
-                               'filename' => 'wordcamp-payments-%s-%s-jpm-ach.ach',
-                       ),
-                       'jpm_checks' => array(
-                               'label' => 'JP Morgan - Quick Checks',
-                               'mime_type' => 'text/csv',
-                               'callback' => array( __CLASS__, '_generate_payment_report_jpm_checks' ),
-                               'filename' => 'wordcamp-payments-%s-%s-jpm-checks.csv',
-                       ),
-               );
-       }
-
-       /**
-        * Process export requests
-        */
-       public static function process_export_request() {
-               if ( empty( $_POST['submit'] ) || 'export' != self::get_current_tab() ) {
-                       return;
-               }
-
-               if ( ! current_user_can( 'manage_network' ) || ! check_admin_referer( 'export', 'wcpn_request_export' ) ) {
-                       return;
-               }
-
-               $export_types = self::get_export_types();
-
-               if ( array_key_exists( $_POST['wcpn_export_type'], $export_types ) ) {
-                       $export_type = $export_types[ $_POST['wcpn_export_type'] ];
-               } else {
-                       $export_type = $export_types['default'];
-               }
-
-               $status = $_POST['wcpn_export_status'];
-               if ( ! in_array( $status, array( 'wcb-approved', 'wcb-paid' ) ) )
-                       $status = 'wcb-approved';
-
-               $start_date = strtotime( $_POST['wcpn_export_start_date'] . ' 00:00:00' );
-               $end_date   = strtotime( $_POST['wcpn_export_end_date']   . ' 23:59:59' );
-               $filename = sprintf( $export_type['filename'], date( 'Ymd', $start_date ), date( 'Ymd', $end_date ) );
-               $filename = sanitize_file_name( $filename );
-
-               $report = self::generate_payment_report( $status, $start_date, $end_date, $export_type );
-
-               if ( is_wp_error( $report ) ) {
-                       add_settings_error( 'wcp-dashboard', $report->get_error_code(), $report->get_error_message() );
-               } else {
-                       header( sprintf( 'Content-Type: %s', $export_type['mime_type'] ) );
-                       header( sprintf( 'Content-Disposition: attachment; filename="%s"', $filename ) );
-                       header( 'Cache-control: private' );
-                       header( 'Pragma: private' );
-                       header( 'Expires: Mon, 26 Jul 1997 05:00:00 GMT' );
-
-                       echo $report;
-                       die();
-               }
-       }
-
-       /*
-        * Generate and return the raw payment report contents
-        *
-        * @param string $date_type 'paid' | 'created'
-        * @param int $start_date
-        * @param int $end_date
-        * @param string $type
-        *
-        * @return string | WP_Error
-        */
-       protected static function generate_payment_report( $status, $start_date, $end_date, $export_type ) {
-               global $wpdb;
-
-               if ( ! is_int( $start_date ) || ! is_int( $end_date ) ) {
-                       return new WP_Error( 'wcpn_bad_dates', 'Invalid start or end date.' );
-               }
-
-               $table_name = self::get_table_name();
-               $date_type = 'updated';
-
-               if ( $status == 'wcb-paid' )
-                       $date_type = 'paid';
-
-               $request_indexes = $wpdb->get_results( $wpdb->prepare( "
-                       SELECT *
-                       FROM   `{$table_name}`
-                       WHERE  `{$date_type}` BETWEEN %d AND %d",
-                       $start_date,
-                       $end_date
-               ) );
-
-               if ( ! is_callable( $export_type['callback'] ) )
-                       return new WP_Error( 'wcpn_invalid_type', 'The export type is invalid.' );
-
-               $args = array(
-                       'request_indexes' => $request_indexes,
-                       'start_date' => $start_date,
-                       'end_date' => $end_date,
-                       'export_type' => $export_type,
-                       'status' => $status,
-               );
-
-               return call_user_func( $export_type['callback'], $args );
-       }
-
-       /**
-        * Default CSV report
-        *
-        * @param array $args
-        *
-        * @return string
-        */
-       protected static function _generate_payment_report_default( $args ) {
-               $args = wp_parse_args( $args, array(
-                       'request_indexes' => array(),
-                       'status' => '',
-               ) );
-
-               $column_headings = array(
-                       'WordCamp', 'ID', 'Title', 'Status', 'Date Vendor was Paid', 'Creation Date', 'Due Date', 'Amount',
-                       'Currency', 'Category', 'Payment Method','Vendor Name', 'Vendor Contact Person', 'Vendor Country',
-                       'Check Payable To', 'URL', 'Supporting Documentation Notes',
-               );
-
-               ob_start();
-               $report = fopen( 'php://output', 'w' );
-
-               fputcsv( $report, $column_headings );
-
-               foreach( $args['request_indexes'] as $index ) {
-                       $row = self::get_report_row( $index, $args );
-                       if ( ! empty( $row ) ) {
-                               fputcsv( $report, $row );
-                       }
-               }
-
-               fclose( $report );
-               return ob_get_clean();
-       }
-
-       /**
-        * Quick Checks via JP Morgan
-        *
-        * @param array $args
-        *
-        * @return string
-        */
-       protected static function _generate_payment_report_jpm_checks( $args ) {
-               $args = wp_parse_args( $args, array(
-                       'request_indexes' => array(),
-                       'status' => '',
-               ) );
-
-               $options = apply_filters( 'wcb_payment_req_check_options', array(
-                       'pws_customer_id' => '',
-                       'account_number'  => '',
-                       'contact_email'   => '',
-                       'contact_phone'   => '',
-               ) );
-
-               $report = fopen( 'php://output', 'w' );
-               ob_start();
-
-               // File Header
-               fputcsv( $report, array( 'FILHDR', 'PWS', $options['pws_customer_id'], date( 'm/d/Y' ), date( 'Hi' ) ), ',', '|' );
-
-               $total = 0;
-               $count = 0;
-
-               if ( false !== get_site_transient( '_wcb_jpm_checks_counter_lock' ) ) {
-                       wp_die( 'JPM Checks Export is locked. Please try again later or contact support.' );
-               }
-
-               // Avoid at least *some* race conditions.
-               set_site_transient( '_wcb_jpm_checks_counter_lock', 1, 30 );
-               $start = absint( get_site_option( '_wcb_jpm_checks_counter', 0 ) );
-
-               foreach ( $args['request_indexes'] as $index ) {
-                       switch_to_blog( $index->blog_id );
-                       $post = get_post( $index->post_id );
-
-                       if ( $args['status'] && $post->post_status != $args['status'] )
-                               continue;
-
-                       if ( get_post_meta( $post->ID, '_camppayments_payment_method', true ) != 'Check' )
-                               continue;
-
-                       $count++;
-                       $amount = round( floatval( get_post_meta( $post->ID, '_camppayments_payment_amount', true ) ), 2 );
-                       $total += $amount;
-
-                       $payable_to = WCP_Encryption::maybe_decrypt( get_post_meta( $post->ID, '_camppayments_payable_to', true ) );
-                       $payable_to = html_entity_decode( $payable_to ); // J&amp;J to J&J
-                       $countries = WordCamp_Budgets::get_valid_countries_iso3166();
-                       $vendor_country_code = get_post_meta( $post->ID, '_camppayments_vendor_country_iso3166', true );
-                       if ( ! empty( $countries[ $vendor_country_code ] ) ) {
-                               $vendor_country_code = $countries[ $vendor_country_code ]['alpha3'];
-                       }
-
-                       $description = sanitize_text_field( get_post_meta( $post->ID, '_camppayments_description', true ) );
-                       $description = html_entity_decode( $description );
-                       $invoice_number = get_post_meta( $post->ID, '_camppayments_invoice_number', true );
-                       if ( ! empty( $invoice_number ) ) {
-                               $description = sprintf( 'Invoice %s. %s', $invoice_number, $description );
-                       }
-
-                       // Payment Header
-                       fputcsv( $report, array(
-                               'PMTHDR',
-                               'USPS',
-                               'QKCHECKS',
-                               date( 'm/d/Y' ),
-                               number_format( $amount, 2, '.', '' ),
-                               $options['account_number'],
-                               $start + $count, // must be globally unique?
-                               $options['contact_email'],
-                               $options['contact_phone'],
-                       ), ',', '|' );
-
-                       // Payee Name Record
-                       fputcsv( $report, array(
-                               'PAYENM',
-                               substr( $payable_to, 0, 35 ),
-                               '',
-                               sprintf( '%d-%d', $index->blog_id, $index->post_id ),
-                       ), ',', '|' );
-
-                       // Payee Address Record
-                       fputcsv( $report, array(
-                               'PYEADD',
-                               substr( get_post_meta( $post->ID, '_camppayments_vendor_street_address', true ), 0, 35 ),
-                               '',
-                       ), ',', '|' );
-
-                       // Additional Payee Address Record
-                       fputcsv( $report, array( 'ADDPYE', '', '' ), ',', '|' );
-
-                       // Payee Postal Record
-                       fputcsv( $report, array(
-                               'PYEPOS',
-                               substr( get_post_meta( $post->ID, '_camppayments_vendor_city', true ), 0, 35 ),
-                               substr( get_post_meta( $post->ID, '_camppayments_vendor_state', true ), 0, 35 ),
-                               substr( get_post_meta( $post->ID, '_camppayments_vendor_zip_code', true ), 0, 10 ),
-                               substr( $vendor_country_code, 0, 3 ),
-                       ), ',', '|' );
-
-                       // Payment Description
-                       fputcsv( $report, array(
-                               'PYTDES',
-                               substr( $description, 0, 122 ),
-                       ), ',', '|' );
-
-                       restore_current_blog();
-               }
-
-               // File Trailer
-               fputcsv( $report, array( 'FILTRL', $count * 6 + 2 ), ',', '|' );
-
-               // Update counter and unlock
-               $start = absint( get_site_option( '_wcb_jpm_checks_counter', 0 ) );
-               update_site_option( '_wcb_jpm_checks_counter', $start + $count );
-               delete_site_transient( '_wcb_jpm_checks_counter_lock' );
-
-               fclose( $report );
-               return ob_get_clean();
-       }
-
-       /**
-        * NACHA via JP Morgan
-        *
-        * @param array $args
-        *
-        * @return string
-        */
-       protected static function _generate_payment_report_jpm_ach( $args ) {
-               $args = wp_parse_args( $args, array(
-                       'request_indexes' => array(),
-                       'status' => '',
-               ) );
-
-               $ach_options = apply_filters( 'wcb_payment_req_ach_options', array(
-                       'bank-routing-number' => '', // Immediate Destination (bank routing number)
-                       'company-id'          => '', // Company ID
-                       'financial-inst'      => '', // Originating Financial Institution
-               ) );
-
-               ob_start();
-
-               // File Header Record
-
-               echo '1'; // Record Type Code
-               echo '01'; // Priority Code
-               echo ' ' . str_pad( substr( $ach_options['bank-routing-number'], 0, 9 ), 9, '0', STR_PAD_LEFT );
-               echo str_pad( substr( $ach_options['company-id'], 0, 10 ), 10, '0', STR_PAD_LEFT ); // Immediate Origin (TIN)
-               echo date( 'ymd' ); // Transmission Date
-               echo date( 'Hi' ); // Transmission Time
-               echo 'A'; // File ID Modifier
-               echo '094'; // Record Size
-               echo '10'; // Blocking Factor
-               echo '1'; // Format Code
-               echo str_pad( 'JPMORGANCHASE', 23 ); // Destination
-               echo str_pad( 'WCEXPORT', 23 ); // Origin
-               echo str_pad( '', 8 ); // Reference Code (optional)
-               echo PHP_EOL;
-
-               // Batch Header Record
-
-               echo '5'; // Record Type Code
-               echo '200'; // Service Type Code
-               echo 'WordCamp Communi'; // Company Name
-               echo str_pad( '', 20 ); // Blanks
-               echo str_pad( substr( $ach_options['company-id'], 0, 10 ), 10 ); // Company Identification
-
-               // Get the first one in the set.
-               // @todo Split batches by account type.
-               foreach ( $args['request_indexes'] as $index ) {
-                       switch_to_blog( $index->blog_id );
-                       $post = get_post( $index->post_id );
-                       $account_type = get_post_meta( $post->ID, '_camppayments_ach_account_type', true );
-                       restore_current_blog();
-
-                       break;
-               }
-
-               $entry_class = $account_type == 'Personal' ? 'PPD' : 'CCD';
-               echo $entry_class; // Standard Entry Class
-
-               echo 'Vendor Pay'; // Entry Description
-               echo date( 'ymd', self::_next_business_day_timestamp() ); // Company Description Date
-               echo date( 'ymd', self::_next_business_day_timestamp() ); // Effective Entry Date
-               echo str_pad( '', 3 ); // Blanks
-               echo '1'; // Originator Status Code
-               echo str_pad( substr( $ach_options['financial-inst'], 0, 8 ), 8 ); // Originating Financial Institution
-               echo '0000001'; // Batch Number
-               echo PHP_EOL;
-
-               $count = 0;
-               $total = 0;
-               $hash = 0;
-
-               foreach ( $args['request_indexes'] as $index ) {
-                       switch_to_blog( $index->blog_id );
-                       $post = get_post( $index->post_id );
-
-                       if ( $args['status'] && $post->post_status != $args['status'] )
-                               continue;
-
-                       if ( get_post_meta( $post->ID, '_camppayments_payment_method', true ) != 'Direct Deposit' )
-                               continue;
-
-                       $count++;
-
-                       // Entry Detail Record
-
-                       echo '6'; // Record Type Code
-
-                       $transaction_code = $account_type == 'Personal' ? '27' : '22';
-                       echo $transaction_code; // Transaction Code
-
-                       // Transit/Routing Number of Destination Bank + Check digit
-                       $routing_number = get_post_meta( $post->ID, '_camppayments_ach_routing_number', true );
-                       $routing_number = WCP_Encryption::maybe_decrypt( $routing_number );
-                       $routing_number = substr( $routing_number, 0, 8 + 1 );
-                       $routing_number = str_pad( $routing_number, 8 + 1 );
-                       $hash += absint( substr( $routing_number, 0, 8 ) );
-                       echo $routing_number;
-
-                       // Bank Account Number
-                       $account_number = get_post_meta( $post->ID, '_camppayments_ach_account_number', true );
-                       $account_number = WCP_Encryption::maybe_decrypt( $account_number );
-                       $account_number = substr( $account_number, 0, 17 );
-                       $account_number = str_pad( $account_number, 17 );
-                       echo $account_number;
-
-                       // Amount
-                       $amount = round( floatval( get_post_meta( $post->ID, '_camppayments_payment_amount', true ) ), 2 );
-                       $total += $amount;
-                       $amount = str_pad( number_format( $amount, 2, '', '' ), 10, '0', STR_PAD_LEFT );
-                       echo $amount;
-
-                       // Individual Identification Number
-                       echo str_pad( sprintf( '%d-%d', $index->blog_id, $index->post_id ), 15 );
-
-                       // Individual Name
-                       $name = get_post_meta( $post->ID, '_camppayments_ach_account_holder_name', true );
-                       $name = WCP_Encryption::maybe_decrypt( $name );
-                       $name = substr( $name, 0, 22 );
-                       $name = str_pad( $name, 22 );
-                       echo $name;
-
-                       echo '  '; // User Defined Data
-                       echo '0'; // Addenda Record Indicator
-
-                       // Trace Number
-                       echo str_pad( substr( $ach_options['bank-routing-number'], 0, 8 ), 8, '0', STR_PAD_LEFT ); // routing number
-                       echo str_pad( $count, 7, '0', STR_PAD_LEFT ); // sequence number
-                       echo PHP_EOL;
-               }
-
-               // Batch Trailer Record
-
-               echo '8'; // Record Type Code
-               echo '200'; // Service Class Code
-               echo str_pad( $count, 6, '0', STR_PAD_LEFT ); // Entry/Addenda Count
-               echo str_pad( substr( $hash, -10 ), 10, '0', STR_PAD_LEFT ); // Entry Hash
-               echo str_pad( number_format( $total, 2, '', '' ), 12, '0', STR_PAD_LEFT ); // Total Debit Entry Dollar Amount
-               echo str_pad( 0, 12, '0', STR_PAD_LEFT ); // Total Credit Entry Dollar Amount
-               echo str_pad( substr( $ach_options['company-id'], 0, 10 ), 10 ); // Company ID
-               echo str_pad( '', 25 ); // Blanks
-               echo str_pad( substr( $ach_options['financial-inst'], 0, 8 ), 8 ); // Originating Financial Institution
-               echo '0000001'; // Batch Number
-               echo PHP_EOL;
-
-
-               // File Trailer Record
-
-               echo '9'; // Record Type Code
-               echo '000001'; // Batch Count
-               echo str_pad( ceil( $count / 10 ), 6, '0', STR_PAD_LEFT ); // Block Count
-               echo str_pad( $count, 8, '0', STR_PAD_LEFT ); // Entry/Addenda Count
-               echo str_pad( substr( $hash, -10 ), 10, '0', STR_PAD_LEFT ); // Entry Hash
-               echo str_pad( number_format( $total, 2, '', '' ), 12, '0', STR_PAD_LEFT ); // Total Debit Entry Dollar Amount
-               echo str_pad( 0, 12, '0', STR_PAD_LEFT ); // Total Credit Entry Dollar Amount
-               echo str_pad( '', 39 ); // Blanks
-               echo PHP_EOL;
-
-               // The file must have a number of lines that is a multiple of 10 (e.g. 10, 20, 30).
-               echo str_repeat( PHP_EOL, 10 - ( ( 4 + $count ) % 10 ) - 1 );
-               return ob_get_clean();
-       }
-
-       /**
-        * Exclude weekends and JPM holidays.
-        *
-        * Needs to be updated every year.
-        *
-        * @return int Timestamp.
-        */
-       private static function _next_business_day_timestamp() {
-               static $timestamp;
-
-               if ( isset( $timestamp ) )
-                       return $timestamp;
-
-               $holidays = array(
-                       date( 'Ymd', strtotime( 'Friday, January 1, 2016' ) ),
-                       date( 'Ymd', strtotime( 'Monday, January 18, 2016' ) ),
-                       date( 'Ymd', strtotime( 'Monday, February 15, 2016' ) ),
-                       date( 'Ymd', strtotime( 'Monday, May 30, 2016' ) ),
-                       date( 'Ymd', strtotime( 'Monday, July 4, 2016' ) ),
-                       date( 'Ymd', strtotime( 'Monday, September 5, 2016' ) ),
-                       date( 'Ymd', strtotime( 'Friday, November 11, 2016' ) ),
-                       date( 'Ymd', strtotime( 'Thursday, November 24, 2016' ) ),
-                       date( 'Ymd', strtotime( 'Monday, December 26, 2016' ) ),
-               );
-
-               $timestamp = strtotime( 'today + 1 weekday' );
-               $attempts = 5;
-
-               while ( in_array( date( 'Ymd', $timestamp ), $holidays ) ) {
-                       $timestamp = strtotime( '+ 1 weekday', $timestamp );
-                       $attempts--;
-
-                       if ( ! $attempts )
-                               break;
-               }
-
-               return $timestamp;
-       }
-
-       /**
-        * Wires via JP Morgan
-        *
-        * @param array $args
-        *
-        * @return string
-        */
-       protected static function _generate_payment_report_jpm_wires( $args ) {
-               $args = wp_parse_args( $args, array(
-                       'request_indexes' => array(),
-                       'status' => '',
-               ) );
-
-               ob_start();
-               $report = fopen( 'php://output', 'w' );
-
-               // JPM Header
-               fputcsv( $report, array( 'HEADER', gmdate( 'YmdHis' ), '1' ) );
-
-               $total = 0;
-               $count = 0;
-
-               foreach ( $args['request_indexes'] as $index ) {
-                       switch_to_blog( $index->blog_id );
-                       $post = get_post( $index->post_id );
-
-                       if ( $args['status'] && $post->post_status != $args['status'] )
-                               continue;
-
-                       // Only wires here.
-                       if ( get_post_meta( $post->ID, '_camppayments_payment_method', true ) != 'Wire' )
-                               continue;
-
-                       $amount = round( floatval( get_post_meta( $post->ID, '_camppayments_payment_amount', true ) ), 2);
-                       $total += $amount;
-                       $count += 1;
-
-                       // If account starts with two letters, it's most likely an IBAN
-                       $account = get_post_meta( $post->ID, '_camppayments_beneficiary_account_number', true );
-                       $account = WCP_Encryption::maybe_decrypt( $account );
-                       $account = preg_replace( '#\s#','', $account );
-                       $account_type = preg_match( '#^[a-z]{2}#i', $account ) ? 'IBAN' : 'ACCT';
-
-                       $row = array(
-                               '1-input-type' => 'P',
-                               '2-payment-method' => 'WIRES',
-                               '3-debit-bank-id' => apply_filters( 'wcb_payment_req_bank_id', '' ), // external file
-                               '4-account-number' => apply_filters( 'wcb_payment_req_bank_number', '' ), // external file
-                               '5-bank-to-bank' => 'N',
-                               '6-txn-currency' => get_post_meta( $post->ID, '_camppayments_currency', true ),
-                               '7-txn-amount' => $amount,
-                               '8-equiv-amount' => '',
-                               '9-clearing' => '',
-                               '10-ben-residence' => '',
-                               '11-rate-type' => '',
-                               '12-blank' => '',
-                               '13-value-date' => '',
-
-                               '14-id-type' => $account_type,
-                               '15-id-value' => $account,
-                               '16-ben-name' => substr( WCP_Encryption::maybe_decrypt(
-                                       get_post_meta( $post->ID, '_camppayments_beneficiary_name', true ) ), 0, 35 ),
-                               '17-address-1' => substr( WCP_Encryption::maybe_decrypt(
-                                       get_post_meta( $post->ID, '_camppayments_beneficiary_street_address', true ) ), 0, 35 ),
-                               '18-address-2' => '',
-                               '19-city-state-zip' => substr( sprintf( '%s %s %s',
-                                               WCP_Encryption::maybe_decrypt( get_post_meta( $post->ID, '_camppayments_beneficiary_city', true ) ),
-                                               WCP_Encryption::maybe_decrypt( get_post_meta( $post->ID, '_camppayments_beneficiary_state', true ) ),
-                                               WCP_Encryption::maybe_decrypt( get_post_meta( $post->ID, '_camppayments_beneficiary_zip_code', true ) )
-                                       ), 0, 32 ),
-                               '20-blank' => '',
-                               '21-country' => WCP_Encryption::maybe_decrypt(
-                                       get_post_meta( $post->ID, '_camppayments_beneficiary_country_iso3166', true ) ),
-                               '22-blank' => '',
-                               '23-blank' => '',
-
-                               '24-id-type' => 'SWIFT',
-                               '25-id-value' => get_post_meta( $post->ID, '_camppayments_bank_bic', true ),
-                               '26-ben-bank-name' => substr( get_post_meta( $post->ID, '_camppayments_bank_name', true ), 0, 35 ),
-                               '27-ben-bank-address-1' => substr( get_post_meta( $post->ID, '_camppayments_bank_street_address', true ), 0, 35 ),
-                               '28-ben-bank-address-2' => '',
-                               '29-ben-bank-address-3' => substr( sprintf( '%s %s %s',
-                                               get_post_meta( $post->ID, '_camppayments_bank_city', true ),
-                                               get_post_meta( $post->ID, '_camppayments_bank_state', true ),
-                                               get_post_meta( $post->ID, '_camppayments_bank_zip_code', true )
-                                       ), 0, 35 ),
-                               '30-ben-bank-country' => get_post_meta( $post->ID, '_camppayments_bank_country_iso3166', true ),
-                               '31-supl-id-type' => '',
-                               '32-supl-id-value' => '',
-
-                               '33-blank' => '',
-                               '34-blank' => '',
-                               '35-blank' => '',
-                               '36-blank' => '',
-                               '37-blank' => '',
-                               '38-blank' => '',
-                               '39-blank' => '',
-
-                               // Filled out later if not empty.
-                               '40-id-type' => '',
-                               '41-id-value' => '',
-                               '42-interm-bank-name' => '',
-                               '43-interm-bank-address-1' => '',
-                               '44-interm-bank-address-2' => '',
-                               '45-interm-bank-address-3' => '',
-                               '46-interm-bank-country' => '',
-                               '47-supl-id-type' => '',
-                               '48-supl-id-value' => '',
-
-                               '49-id-type' => '',
-                               '50-id-value' => '',
-                               '51-party-name' => '',
-                               '52-party-address-1' => '',
-                               '53-party-address-2' => '',
-                               '54-party-address-3' => '',
-                               '55-party-country' => '',
-
-                               '56-blank' => '',
-                               '57-blank' => '',
-                               '58-blank' => '',
-                               '59-blank' => '',
-                               '60-blank' => '',
-                               '61-blank' => '',
-                               '62-blank' => '',
-                               '63-blank' => '',
-                               '64-blank' => '',
-                               '65-blank' => '',
-                               '66-blank' => '',
-                               '67-blank' => '',
-                               '68-blank' => '',
-                               '69-blank' => '',
-                               '70-blank' => '',
-                               '71-blank' => '',
-                               '72-blank' => '',
-                               '73-blank' => '',
-
-                               '74-ref-text' => substr( get_post_meta( $post->ID, '_camppayments_invoice_number', true ), 0, 16 ),
-                               '75-internal-ref' => '',
-                               '76-on-behalf-of' => '',
-
-                               '77-detial-1' => '',
-                               '78-detial-2' => '',
-                               '79-detial-3' => '',
-                               '80-detail-4' => '',
-
-                               '81-blank' => '',
-                               '82-blank' => '',
-                               '83-blank' => '',
-                               '84-blank' => '',
-                               '85-blank' => '',
-                               '86-blank' => '',
-                               '87-blank' => '',
-                               '88-blank' => '',
-
-                               '89-reporting-code' => '',
-                               '90-country' => '',
-                               '91-inst-1' => '',
-                               '92-inst-2' => '',
-                               '93-inst-3' => '',
-                               '94-inst-code-1' => '',
-                               '95-inst-text-1' => '',
-                               '96-inst-code-2' => '',
-                               '97-inst-text-2' => '',
-                               '98-inst-code-3' => '',
-                               '99-inst-text-3' => '',
-
-                               '100-stor-code-1' => '',
-                               '101-stor-line-2' => '', // Hmm?
-                               '102-stor-code-2' => '',
-                               '103-stor-line-2' => '',
-                               '104-stor-code-3' => '',
-                               '105-stor-line-3' => '',
-                               '106-stor-code-4' => '',
-                               '107-stor-line-4' => '',
-                               '108-stor-code-5' => '',
-                               '109-stor-line-5' => '',
-                               '110-stor-code-6' => '',
-                               '111-stor-line-6' => '',
-
-                               '112-priority' => '',
-                               '113-blank' => '',
-                               '114-charges' => '',
-                               '115-blank' => '',
-                               '116-details' => '',
-                               '117-note' => substr( sprintf( 'wcb-%d-%d', $index->blog_id, $index->post_id ), 0, 70 ),
-                       );
-
-                       // If an intermediary bank is given.
-                       $interm_swift = get_post_meta( $post->ID, '_camppayments_interm_bank_swift', true );
-                       if ( ! empty( $iterm_swift ) ) {
-                               $row['40-id-type'] = 'SWIFT';
-                               $row['41-id-value'] = $interm_swift;
-
-                               $row['42-interm-bank-name'] = substr( get_post_meta( $post->ID, '_camppayments_interm_bank_name', true ), 0, 35 );
-                               $row['43-interm-bank-address-1'] = substr( get_post_meta( $post->ID, '_camppayments_interm_bank_street_address', true ), 0, 35 );
-
-                               $row['44-interm-bank-address-2'] = '';
-                               $row['45-interm-bank-address-3'] = substr( sprintf( '%s %s %s',
-                                       get_post_meta( $post->ID, '_camppayments_interm_bank_city', true ),
-                                       get_post_meta( $post->ID, '_camppayments_interm_bank_state', true ),
-                                       get_post_meta( $post->ID, '_camppayments_interm_bank_zip_code', true )
-                               ), 0, 32 );
-
-                               $row['46-interm-bank-country'] = get_post_meta( $post->ID, '_camppayments_interm_bank_country_iso3166', true );
-
-                               $row['47-supl-id-type'] = 'ACCT';
-                               $row['48-supl-id-value'] = get_post_meta( $post->ID, '_camppayments_interm_bank_account', true );
-                       }
-
-                       // Because CSV is stupid:
-                       // print_r( $row );
-
-                       fputcsv( $report, array_values( $row ) );
-                       restore_current_blog();
-               }
-
-               // JPM Trailer
-               fputcsv( $report, array( 'TRAILER', $count, $total ) );
-
-               fclose( $report );
-               $results = ob_get_clean();
-
-               // JPM chokes on accents and non-latin characters.
-               $results = remove_accents( $results );
-               return $results;
-       }
-
-       /**
-        * Gather all the request details needed for a row in the export file
-        *
-        * @param stdClass $index
-        * @param array $args
-        *
-        * @return array
-        */
-       protected static function get_report_row( $index, $args ) {
-               switch_to_blog( $index->blog_id );
-
-               $request = get_post( $index->post_id );
-
-               $back_compat_statuses = array(
-                       'unpaid' => 'draft',
-                       'incomplete' => 'wcb-incomplete',
-                       'paid' => 'wcb-paid',
-               );
-
-               // Map old statuses to new statuses.
-               if ( array_key_exists( $request->post_status, $back_compat_statuses ) ) {
-                       $request->post_status = $back_compat_statuses[ $request->post_status ];
-               }
-
-               if ( $args['status'] && $request->post_status != $args['status'] ) {
-                       return null;
-               }
-
-               $currency         = get_post_meta( $index->post_id, '_camppayments_currency',         true );
-               $category         = get_post_meta( $index->post_id, '_camppayments_payment_category', true );
-               $date_vendor_paid = get_post_meta( $index->post_id, '_camppayments_date_vendor_paid', true );
-
-               if ( $date_vendor_paid ) {
-                       $date_vendor_paid = date( 'Y-m-d', $date_vendor_paid );
-               }
-
-               if ( 'null-select-one' === $currency ) {
-                       $currency = '';
-               }
-
-               if ( 'null' === $category ) {
-                       $category = '';
-               }
-
-               $row = array(
-                       get_wordcamp_name(),
-                       sprintf( '%d-%d', $index->blog_id, $index->post_id ),
-                       $request->post_title,
-                       $index->status,
-                       $date_vendor_paid,
-                       date( 'Y-m-d', $index->created ),
-                       date( 'Y-m-d', $index->due ),
-                       get_post_meta( $index->post_id, '_camppayments_payment_amount', true ),
-                       $currency,
-                       $category,
-                       get_post_meta( $index->post_id, '_camppayments_payment_method', true ),
-                       get_post_meta( $index->post_id, '_camppayments_vendor_name', true ),
-                       get_post_meta( $index->post_id, '_camppayments_vendor_contact_person', true ),
-                       get_post_meta( $index->post_id, '_camppayments_vendor_country', true ),
-                       WCP_Encryption::maybe_decrypt( get_post_meta( $index->post_id, '_camppayments_payable_to', true ) ),
-                       get_edit_post_link( $index->post_id ),
-                       get_post_meta( $index->post_id, '_camppayments_file_notes', true ),
-               );
-
-               restore_current_blog();
-
-               return $row;
-       }
-
-       /**
-        * Render the Export tab
-        */
-       protected static function render_export_tab() {
-               $today      = date( 'Y-m-d' );
-               $last_month = date( 'Y-m-d', strtotime( 'now - 1 month' ) );
-               ?>
-
-               <script>
-                       /**
-                        * Fallback to the jQueryUI datepicker if the browser doesn't support <input type="date">
-                        */
-                       jQuery( document ).ready( function( $ ) {
-                               var browserTest = document.createElement( 'input' );
-                               browserTest.setAttribute( 'type', 'date' );
-
-                               if ( 'text' === browserTest.type ) {
-                                       $( '#wcpn_export' ).find( 'input[type=date]' ).datepicker( {
-                                               dateFormat : 'yy-mm-dd',
-                                               changeMonth: true,
-                                               changeYear : true
-                                       } );
-                               }
-                       } );
-               </script>
-
-               <form id="wcpn_export" method="POST">
-                       <?php wp_nonce_field( 'export', 'wcpn_request_export' ); ?>
-
-                       <h2>Export Settings</h2>
-
-                       <table class="form-table">
-                               <tr>
-                                       <th><label>Status</label></th>
-                                       <td>
-                                               <select name="wcpn_export_status">
-                                                       <option value="wcb-approved"><?php _e( 'Approved', 'wordcamporg' ); ?></option>
-                                                       <option value="wcb-paid"><?php _e( 'Paid', 'wordcamporg' ); ?></option>
-                                               </select>
-                                       </td>
-                               </tr>
-                               <tr>
-                                       <th><label>Date Range</label></th>
-                                       <td>
-                                               <input type="date" name="wcpn_export_start_date" class="medium-text" value="<?php echo esc_attr( $last_month ); ?>" /> to
-                                               <input type="date" name="wcpn_export_end_date" class="medium-text" value="<?php echo esc_attr( $today ); ?>" />
-                                       </td>
-                               </tr>
-                               <tr>
-                                       <th><label>Format</label></th>
-                                       <td>
-                                               <select name="wcpn_export_type">
-                                                       <?php foreach ( self::get_export_types() as $key => $export_type ) : ?>
-                                                       <option value="<?php echo esc_attr( $key ); ?>"><?php echo esc_html( $export_type['label'] ); ?></option>
-                                                       <?php endforeach; ?>
-                                               </select>
-                                       </td>
-                               </tr>
-                       </table>
-
-                       <?php submit_button( 'Download Export' ); ?>
-               </form>
-
-               <?php
-       }
-
-       /**
</del><span class="cx" style="display: block; padding: 0 10px">          * Renders the import tab.
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="cx" style="display: block; padding: 0 10px">        public static function render_import_tab() {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1397,7 +529,6 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        'cancelled-failed',
</span><span class="cx" style="display: block; padding: 0 10px">                        'incomplete',
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        'export',
</del><span class="cx" style="display: block; padding: 0 10px">                         'import',
</span><span class="cx" style="display: block; padding: 0 10px">                );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1422,7 +553,6 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        'paid'             => __( 'Paid', 'wordcamporg' ),
</span><span class="cx" style="display: block; padding: 0 10px">                        'cancelled-failed' => __( 'Cancelled/Failed', 'wordcamporg' ),
</span><span class="cx" style="display: block; padding: 0 10px">                        'incomplete'       => __( 'Incomplete', 'wordcamporg' ),
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        'export'           => __( 'Export', 'wordcamporg' ),
</del><span class="cx" style="display: block; padding: 0 10px">                         'import'           => __( 'Import', 'wordcamporg' ),
</span><span class="cx" style="display: block; padding: 0 10px">                );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span></span></pre></div>
<a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcamppaymentsnetworkincludeswordcampbudgetsdashboardphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-payments-network/includes/wordcamp-budgets-dashboard.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-payments-network/includes/wordcamp-budgets-dashboard.php 2016-03-03 23:05:06 UTC (rev 2693)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-payments-network/includes/wordcamp-budgets-dashboard.php   2016-03-04 10:34:58 UTC (rev 2694)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -7,10 +7,13 @@
</span><span class="cx" style="display: block; padding: 0 10px">  * Core functionality and helper functions shared between modules
</span><span class="cx" style="display: block; padding: 0 10px">  */
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-add_action( 'network_admin_menu',    __NAMESPACE__ . '\register_budgets_menu' );
-add_action( 'network_admin_menu',    __NAMESPACE__ . '\remove_budgets_submenu', 11 ); // after other modules have registered their submenu pages
-add_action( 'admin_enqueue_scripts', __NAMESPACE__ . '\enqueue_scripts' );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+add_action( 'network_admin_menu', __NAMESPACE__ . '\register_budgets_menu' );
+add_action( 'network_admin_menu', __NAMESPACE__ . '\remove_budgets_submenu', 11 ); // after other modules have registered their submenu pages
+add_action( 'network_admin_menu', __NAMESPACE__ . '\import_export_admin_menu', 11 );
+add_action( 'admin_enqueue_scripts', __NAMESPACE__ . '\enqueue_scripts', 10, 1 );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+add_action( 'admin_init', __NAMESPACE__ . '\process_export_request' );
+
</ins><span class="cx" style="display: block; padding: 0 10px"> /**
</span><span class="cx" style="display: block; padding: 0 10px">  * Register the Budgets Dashboard menu
</span><span class="cx" style="display: block; padding: 0 10px">  *
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -31,24 +34,960 @@
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * Register the Import/Export dashboard menu item.
+ */
+function import_export_admin_menu() {
+       add_submenu_page(
+               'wordcamp-budgets-dashboard',
+               'WordCamp Budgets Import/Export',
+               'Import/Export',
+               'manage_network',
+               'wcb-import-export',
+               __NAMESPACE__ . '\render_import_export'
+       );
+}
+
+/**
+ * Render the import/export screen.
+ */
+function render_import_export() {
+       $current_tab = 'import';
+       if ( ! empty( $_GET['section'] ) && in_array( $_GET['section'], array( 'import', 'export' ) ) ) {
+               $current_tab = $_GET['section'];
+       }
+
+       ?>
+               <div class="wrap">
+                       <h1>Import/Export</h1>
+
+                       <?php do_action( 'admin_notices' ); ?>
+                       <?php settings_errors(); ?>
+
+                       <h3 class="nav-tab-wrapper">
+                               <a class="nav-tab <?php if ( $current_tab == 'import' ) { echo 'nav-tab-active'; } ?>"
+                                       href="<?php echo add_query_arg( array(
+                                               'page' => 'wcb-import-export',
+                                               'section' => 'import',
+                                       ), network_admin_url( 'admin.php' ) ); ?>">Import</a>
+
+                               <a class="nav-tab <?php if ( $current_tab == 'export' ) { echo 'nav-tab-active'; } ?>"
+                                       href="<?php echo add_query_arg( array(
+                                               'page' => 'wcb-import-export',
+                                               'section' => 'export',
+                                       ), network_admin_url( 'admin.php' ) ); ?>">Export</a>
+                       </h3>
+
+                       <?php
+                               if ( 'export' == $current_tab ) {
+                                       render_export_tab();
+                               } elseif ( 'import' == $current_tab ) {
+                                       render_import_tab();
+                               }
+                       ?>
+
+               </div> <!-- /wrap -->
+       <?php
+}
+
+/**
+ * Get available export options.
+ *
+ * @return array
+ */
+function get_export_types() {
+       return array(
+               'default' => array(
+                       'label' => 'Regular CSV',
+                       'mime_type' => 'text/csv',
+                       'callback' => __NAMESPACE__ . '\_generate_payment_report_default',
+                       'filename' => 'wordcamp-payments-%s-%s-default.csv',
+               ),
+               'jpm_wires' => array(
+                       'label' => 'JP Morgan Access - Wire Payments',
+                       'mime_type' => 'text/csv',
+                       'callback' => __NAMESPACE__ . '\_generate_payment_report_jpm_wires',
+                       'filename' => 'wordcamp-payments-%s-%s-jpm-wires.csv',
+               ),
+               'jpm_ach' => array(
+                       'label' => 'JP Morgan - NACHA',
+                       'mime_type' => 'text/plain',
+                       'callback' => __NAMESPACE__ . '\_generate_payment_report_jpm_ach',
+                       'filename' => 'wordcamp-payments-%s-%s-jpm-ach.ach',
+               ),
+               'jpm_checks' => array(
+                       'label' => 'JP Morgan - Quick Checks',
+                       'mime_type' => 'text/csv',
+                       'callback' => __NAMESPACE__ . '\_generate_payment_report_jpm_checks',
+                       'filename' => 'wordcamp-payments-%s-%s-jpm-checks.csv',
+               ),
+       );
+}
+
+/**
+ * Render the Import tab
+ */
+function render_import_tab() {
+       echo '<p>Move along, nothing to see here.</p>';
+}
+
+/**
+ * Render the export tab
+ */
+function render_export_tab() {
+               $today = date( 'Y-m-d' );
+               $last_month = date( 'Y-m-d', strtotime( 'now - 1 month' ) );
+               ?>
+               <script>
+               /**
+                * Fallback to the jQueryUI datepicker if the browser doesn't support <input type="date">
+                */
+               jQuery( document ).ready( function( $ ) {
+                       var browserTest = document.createElement( 'input' );
+                       browserTest.setAttribute( 'type', 'date' );
+
+                       if ( 'text' === browserTest.type ) {
+                               $( '#wcpn_export' ).find( 'input[type=date]' ).datepicker( {
+                                       dateFormat : 'yy-mm-dd',
+                                       changeMonth: true,
+                                       changeYear : true
+                               } );
+                       }
+               } );
+               </script>
+
+               <form id="wcpn_export" method="POST">
+                       <?php wp_nonce_field( 'export', 'wcb-request-export' ); ?>
+
+                       <h2>Export Settings</h2>
+
+                       <table class="form-table">
+                               <tr>
+                                       <th>Types</th>
+                                       <td>
+                                               <label><input type="checkbox" name="wcb-export-types-vendor-payments"
+                                                       value="1" checked disabled /> Vendor Payments</label><br />
+                                               <label><input type="checkbox" name="wcb-export-types-reimbursements"
+                                                       value="1" disabled /> Reimbursements</label>
+                                       </td>
+                               <tr>
+                                       <th>Status</th>
+                                       <td>
+                                               <select name="wcb-export-status">
+                                                       <option value="wcb-approved"><?php _e( 'Approved', 'wordcamporg' ); ?></option>
+                                                       <option value="wcb-paid"><?php _e( 'Paid', 'wordcamporg' ); ?></option>
+                                               </select>
+                                       </td>
+                               </tr>
+                               <tr>
+                                       <th>Date Range</th>
+                                       <td>
+                                               <input type="date" name="wcb-export-start-date"
+                                                       class="medium-text" value="<?php echo esc_attr( $last_month ); ?>" /> to
+                                               <input type="date" name="wcb-export-end-date"
+                                                       class="medium-text" value="<?php echo esc_attr( $today ); ?>" />
+                                       </td>
+                               </tr>
+                               <tr>
+                                       <th>Format</th>
+                                       <td>
+                                               <select name="wcb-export-type">
+                                                       <?php foreach ( get_export_types() as $key => $export_type ) : ?>
+                                                       <option value="<?php echo esc_attr( $key ); ?>"><?php echo esc_html( $export_type['label'] ); ?></option>
+                                                       <?php endforeach; ?>
+                                               </select>
+                                       </td>
+                               </tr>
+                       </table>
+
+                       <?php submit_button( 'Download Export' ); ?>
+               </form>
+               <?php
+}
+
+/**
+ * Process export requests
+ */
+function process_export_request() {
+       if ( empty( $_GET['page'] ) || $_GET['page'] != 'wcb-import-export' )
+               return;
+
+       if ( empty( $_GET['section'] ) || $_GET['section'] != 'export' )
+               return;
+
+       if ( empty( $_POST['wcb-request-export'] ) )
+               return;
+
+       if ( ! current_user_can( 'manage_network' ) || ! check_admin_referer( 'export', 'wcb-request-export' ) )
+               return;
+
+       $export_types = get_export_types();
+
+       if ( array_key_exists( $_POST['wcb-export-type'], $export_types ) ) {
+               $export_type = $export_types[ $_POST['wcb-export-type'] ];
+       } else {
+               $export_type = $export_types['default'];
+       }
+
+       $status = $_POST['wcb-export-status'];
+       if ( ! in_array( $status, array( 'wcb-approved', 'wcb-paid' ) ) )
+               $status = 'wcb-approved';
+
+       $start_date = strtotime( $_POST['wcb-export-start-date'] . ' 00:00:00' );
+       $end_date   = strtotime( $_POST['wcb-export-end-date']   . ' 23:59:59' );
+       $filename = sprintf( $export_type['filename'], date( 'Ymd', $start_date ), date( 'Ymd', $end_date ) );
+       $filename = sanitize_file_name( $filename );
+
+       $report = generate_payment_report( array(
+               'status' => $status,
+               'start_date' => $start_date,
+               'end_date' => $end_date,
+               'export_type' => $export_type,
+       ) );
+
+       if ( is_wp_error( $report ) ) {
+               add_settings_error( 'wcb-dashboard', $report->get_error_code(), $report->get_error_message() );
+       } else {
+               header( sprintf( 'Content-Type: %s', $export_type['mime_type'] ) );
+               header( sprintf( 'Content-Disposition: attachment; filename="%s"', $filename ) );
+               header( 'Cache-control: private' );
+               header( 'Pragma: private' );
+               header( 'Expires: Mon, 26 Jul 1997 05:00:00 GMT' );
+
+               echo $report;
+               die();
+       }
+}
+
+/*
+ * Generate and return the raw payment report contents
+ *
+ * @param array $args
+ *
+ * @return string | WP_Error
+ */
+function generate_payment_report( $args ) {
+       global $wpdb;
+
+       $args = wp_parse_args( $args, array(
+               'status'      => '',
+               'start_date'  => '',
+               'end_date'    => '',
+               'export_type' => '',
+       ) );
+
+       if ( ! is_int( $args['start_date'] ) || ! is_int( $args['end_date'] ) ) {
+               return new WP_Error( 'wcb-bad-dates', 'Invalid start or end date.' );
+       }
+
+       // todo: support other index tables.
+       $table_name = $wpdb->get_blog_prefix(0) . 'wordcamp_payments_index';
+       $date_type = 'updated';
+
+       if ( $args['status'] == 'wcb-paid' )
+               $date_type = 'paid';
+
+       $request_indexes = $wpdb->get_results( $wpdb->prepare( "
+               SELECT *
+               FROM   `{$table_name}`
+               WHERE  `{$date_type}` BETWEEN %d AND %d",
+               $args['start_date'],
+               $args['end_date']
+       ) );
+
+       if ( ! is_callable( $args['export_type']['callback'] ) )
+               return new \WP_Error( 'wcb-invalid-type', 'The export type is invalid.' );
+
+       $args['request_indexes'] = $request_indexes;
+
+       return call_user_func( $args['export_type']['callback'], $args );
+}
+
+/**
+ * Default CSV report
+ *
+ * @param array $args
+ *
+ * @return string
+ */
+function _generate_payment_report_default( $args ) {
+       $args = wp_parse_args( $args, array(
+               'request_indexes' => array(),
+               'status' => '',
+       ) );
+
+       $column_headings = array(
+               'WordCamp', 'ID', 'Title', 'Status', 'Date Vendor was Paid', 'Creation Date', 'Due Date', 'Amount',
+               'Currency', 'Category', 'Payment Method','Vendor Name', 'Vendor Contact Person', 'Vendor Country',
+               'Check Payable To', 'URL', 'Supporting Documentation Notes',
+       );
+
+       ob_start();
+       $report = fopen( 'php://output', 'w' );
+
+       fputcsv( $report, $column_headings );
+
+       foreach( $args['request_indexes'] as $index ) {
+               switch_to_blog( $index->blog_id );
+
+               $request = get_post( $index->post_id );
+
+               $back_compat_statuses = array(
+                       'unpaid' => 'draft',
+                       'incomplete' => 'wcb-incomplete',
+                       'paid' => 'wcb-paid',
+               );
+
+               // Map old statuses to new statuses.
+               if ( array_key_exists( $request->post_status, $back_compat_statuses ) ) {
+                       $request->post_status = $back_compat_statuses[ $request->post_status ];
+               }
+
+               if ( $args['status'] && $request->post_status != $args['status'] ) {
+                       restore_current_blog();
+                       continue;
+               }
+
+               $currency = get_post_meta( $index->post_id, '_camppayments_currency', true );
+               $category = get_post_meta( $index->post_id, '_camppayments_payment_category', true );
+               $date_vendor_paid = get_post_meta( $index->post_id, '_camppayments_date_vendor_paid', true );
+
+               if ( $date_vendor_paid ) {
+                       $date_vendor_paid = date( 'Y-m-d', $date_vendor_paid );
+               }
+
+               if ( 'null-select-one' === $currency ) {
+                       $currency = '';
+               }
+
+               if ( 'null' === $category ) {
+                       $category = '';
+               }
+
+               $row = array(
+                       get_wordcamp_name(),
+                       sprintf( '%d-%d', $index->blog_id, $index->post_id ),
+                       html_entity_decode( $request->post_title ),
+                       $index->status,
+                       $date_vendor_paid,
+                       date( 'Y-m-d', $index->created ),
+                       date( 'Y-m-d', $index->due ),
+                       get_post_meta( $index->post_id, '_camppayments_payment_amount', true ),
+                       $currency,
+                       $category,
+                       get_post_meta( $index->post_id, '_camppayments_payment_method', true ),
+                       get_post_meta( $index->post_id, '_camppayments_vendor_name', true ),
+                       get_post_meta( $index->post_id, '_camppayments_vendor_contact_person', true ),
+                       get_post_meta( $index->post_id, '_camppayments_vendor_country', true ),
+                       \WCP_Encryption::maybe_decrypt( get_post_meta( $index->post_id, '_camppayments_payable_to', true ) ),
+                       get_edit_post_link( $index->post_id ),
+                       get_post_meta( $index->post_id, '_camppayments_file_notes', true ),
+               );
+
+               restore_current_blog();
+
+               if ( ! empty( $row ) ) {
+                       fputcsv( $report, $row );
+               }
+       }
+
+       fclose( $report );
+       return ob_get_clean();
+}
+
+/**
+ * Quick Checks via JP Morgan
+ *
+ * @param array $args
+ *
+ * @return string
+ */
+function _generate_payment_report_jpm_checks( $args ) {
+       $args = wp_parse_args( $args, array(
+               'request_indexes' => array(),
+               'status' => '',
+       ) );
+
+       $options = apply_filters( 'wcb_payment_req_check_options', array(
+               'pws_customer_id' => '',
+               'account_number'  => '',
+               'contact_email'   => '',
+               'contact_phone'   => '',
+       ) );
+
+       $report = fopen( 'php://output', 'w' );
+       ob_start();
+
+       // File Header
+       fputcsv( $report, array( 'FILHDR', 'PWS', $options['pws_customer_id'], date( 'm/d/Y' ), date( 'Hi' ) ), ',', '|' );
+
+       $total = 0;
+       $count = 0;
+
+       if ( false !== get_site_transient( '_wcb_jpm_checks_counter_lock' ) ) {
+               wp_die( 'JPM Checks Export is locked. Please try again later or contact support.' );
+       }
+
+       // Avoid at least *some* race conditions.
+       set_site_transient( '_wcb_jpm_checks_counter_lock', 1, 30 );
+       $start = absint( get_site_option( '_wcb_jpm_checks_counter', 0 ) );
+
+       foreach ( $args['request_indexes'] as $index ) {
+               switch_to_blog( $index->blog_id );
+               $post = get_post( $index->post_id );
+
+               if ( $args['status'] && $post->post_status != $args['status'] ) {
+                       restore_current_blog();
+                       continue;
+               }
+
+               if ( get_post_meta( $post->ID, '_camppayments_payment_method', true ) != 'Check' ) {
+                       restore_current_blog();
+                       continue;
+               }
+
+               $count++;
+               $amount = round( floatval( get_post_meta( $post->ID, '_camppayments_payment_amount', true ) ), 2 );
+               $total += $amount;
+
+               $payable_to = \WCP_Encryption::maybe_decrypt( get_post_meta( $post->ID, '_camppayments_payable_to', true ) );
+               $payable_to = html_entity_decode( $payable_to ); // J&amp;J to J&J
+               $countries = \WordCamp_Budgets::get_valid_countries_iso3166();
+               $vendor_country_code = get_post_meta( $post->ID, '_camppayments_vendor_country_iso3166', true );
+               if ( ! empty( $countries[ $vendor_country_code ] ) ) {
+                       $vendor_country_code = $countries[ $vendor_country_code ]['alpha3'];
+               }
+
+               $description = sanitize_text_field( get_post_meta( $post->ID, '_camppayments_description', true ) );
+               $description = html_entity_decode( $description );
+               $invoice_number = get_post_meta( $post->ID, '_camppayments_invoice_number', true );
+               if ( ! empty( $invoice_number ) ) {
+                       $description = sprintf( 'Invoice %s. %s', $invoice_number, $description );
+               }
+
+               // Payment Header
+               fputcsv( $report, array(
+                       'PMTHDR',
+                       'USPS',
+                       'QKCHECKS',
+                       date( 'm/d/Y' ),
+                       number_format( $amount, 2, '.', '' ),
+                       $options['account_number'],
+                       $start + $count, // must be globally unique?
+                       $options['contact_email'],
+                       $options['contact_phone'],
+               ), ',', '|' );
+
+               // Payee Name Record
+               fputcsv( $report, array(
+                       'PAYENM',
+                       substr( $payable_to, 0, 35 ),
+                       '',
+                       sprintf( '%d-%d', $index->blog_id, $index->post_id ),
+               ), ',', '|' );
+
+               // Payee Address Record
+               fputcsv( $report, array(
+                       'PYEADD',
+                       substr( get_post_meta( $post->ID, '_camppayments_vendor_street_address', true ), 0, 35 ),
+                       '',
+               ), ',', '|' );
+
+               // Additional Payee Address Record
+               fputcsv( $report, array( 'ADDPYE', '', '' ), ',', '|' );
+
+               // Payee Postal Record
+               fputcsv( $report, array(
+                       'PYEPOS',
+                       substr( get_post_meta( $post->ID, '_camppayments_vendor_city', true ), 0, 35 ),
+                       substr( get_post_meta( $post->ID, '_camppayments_vendor_state', true ), 0, 35 ),
+                       substr( get_post_meta( $post->ID, '_camppayments_vendor_zip_code', true ), 0, 10 ),
+                       substr( $vendor_country_code, 0, 3 ),
+               ), ',', '|' );
+
+               // Payment Description
+               fputcsv( $report, array(
+                       'PYTDES',
+                       substr( $description, 0, 122 ),
+               ), ',', '|' );
+
+               restore_current_blog();
+       }
+
+       // File Trailer
+       fputcsv( $report, array( 'FILTRL', $count * 6 + 2 ), ',', '|' );
+
+       // Update counter and unlock
+       $start = absint( get_site_option( '_wcb_jpm_checks_counter', 0 ) );
+       update_site_option( '_wcb_jpm_checks_counter', $start + $count );
+       delete_site_transient( '_wcb_jpm_checks_counter_lock' );
+
+       fclose( $report );
+       return ob_get_clean();
+}
+
+/**
+ * NACHA via JP Morgan
+ *
+ * @param array $args
+ *
+ * @return string
+ */
+function _generate_payment_report_jpm_ach( $args ) {
+       $args = wp_parse_args( $args, array(
+               'request_indexes' => array(),
+               'status' => '',
+       ) );
+
+       $ach_options = apply_filters( 'wcb_payment_req_ach_options', array(
+               'bank-routing-number' => '', // Immediate Destination (bank routing number)
+               'company-id'          => '', // Company ID
+               'financial-inst'      => '', // Originating Financial Institution
+       ) );
+
+       ob_start();
+
+       // File Header Record
+
+       echo '1'; // Record Type Code
+       echo '01'; // Priority Code
+       echo ' ' . str_pad( substr( $ach_options['bank-routing-number'], 0, 9 ), 9, '0', STR_PAD_LEFT );
+       echo str_pad( substr( $ach_options['company-id'], 0, 10 ), 10, '0', STR_PAD_LEFT ); // Immediate Origin (TIN)
+       echo date( 'ymd' ); // Transmission Date
+       echo date( 'Hi' ); // Transmission Time
+       echo 'A'; // File ID Modifier
+       echo '094'; // Record Size
+       echo '10'; // Blocking Factor
+       echo '1'; // Format Code
+       echo str_pad( 'JPMORGANCHASE', 23 ); // Destination
+       echo str_pad( 'WCEXPORT', 23 ); // Origin
+       echo str_pad( '', 8 ); // Reference Code (optional)
+       echo PHP_EOL;
+
+       // Batch Header Record
+
+       echo '5'; // Record Type Code
+       echo '200'; // Service Type Code
+       echo 'WordCamp Communi'; // Company Name
+       echo str_pad( '', 20 ); // Blanks
+       echo str_pad( substr( $ach_options['company-id'], 0, 10 ), 10 ); // Company Identification
+
+       // Get the first one in the set.
+       // @todo Split batches by account type.
+       foreach ( $args['request_indexes'] as $index ) {
+               switch_to_blog( $index->blog_id );
+               $post = get_post( $index->post_id );
+               $account_type = get_post_meta( $post->ID, '_camppayments_ach_account_type', true );
+               restore_current_blog();
+
+               break;
+       }
+
+       $entry_class = $account_type == 'Personal' ? 'PPD' : 'CCD';
+       echo $entry_class; // Standard Entry Class
+
+       echo 'Vendor Pay'; // Entry Description
+       echo date( 'ymd', _next_business_day_timestamp() ); // Company Description Date
+       echo date( 'ymd', _next_business_day_timestamp() ); // Effective Entry Date
+       echo str_pad( '', 3 ); // Blanks
+       echo '1'; // Originator Status Code
+       echo str_pad( substr( $ach_options['financial-inst'], 0, 8 ), 8 ); // Originating Financial Institution
+       echo '0000001'; // Batch Number
+       echo PHP_EOL;
+
+       $count = 0;
+       $total = 0;
+       $hash = 0;
+
+       foreach ( $args['request_indexes'] as $index ) {
+               switch_to_blog( $index->blog_id );
+               $post = get_post( $index->post_id );
+
+               if ( $args['status'] && $post->post_status != $args['status'] ) {
+                       restore_current_blog();
+                       continue;
+               }
+
+               if ( get_post_meta( $post->ID, '_camppayments_payment_method', true ) != 'Direct Deposit' ) {
+                       restore_current_blog();
+                       continue;
+               }
+
+               $count++;
+
+               // Entry Detail Record
+
+               echo '6'; // Record Type Code
+
+               $transaction_code = $account_type == 'Personal' ? '27' : '22';
+               echo $transaction_code; // Transaction Code
+
+               // Transit/Routing Number of Destination Bank + Check digit
+               $routing_number = get_post_meta( $post->ID, '_camppayments_ach_routing_number', true );
+               $routing_number = \WCP_Encryption::maybe_decrypt( $routing_number );
+               $routing_number = substr( $routing_number, 0, 8 + 1 );
+               $routing_number = str_pad( $routing_number, 8 + 1 );
+               $hash += absint( substr( $routing_number, 0, 8 ) );
+               echo $routing_number;
+
+               // Bank Account Number
+               $account_number = get_post_meta( $post->ID, '_camppayments_ach_account_number', true );
+               $account_number = \WCP_Encryption::maybe_decrypt( $account_number );
+               $account_number = substr( $account_number, 0, 17 );
+               $account_number = str_pad( $account_number, 17 );
+               echo $account_number;
+
+               // Amount
+               $amount = round( floatval( get_post_meta( $post->ID, '_camppayments_payment_amount', true ) ), 2 );
+               $total += $amount;
+               $amount = str_pad( number_format( $amount, 2, '', '' ), 10, '0', STR_PAD_LEFT );
+               echo $amount;
+
+               // Individual Identification Number
+               echo str_pad( sprintf( '%d-%d', $index->blog_id, $index->post_id ), 15 );
+
+               // Individual Name
+               $name = get_post_meta( $post->ID, '_camppayments_ach_account_holder_name', true );
+               $name = \WCP_Encryption::maybe_decrypt( $name );
+               $name = substr( $name, 0, 22 );
+               $name = str_pad( $name, 22 );
+               echo $name;
+
+               echo '  '; // User Defined Data
+               echo '0'; // Addenda Record Indicator
+
+               // Trace Number
+               echo str_pad( substr( $ach_options['bank-routing-number'], 0, 8 ), 8, '0', STR_PAD_LEFT ); // routing number
+               echo str_pad( $count, 7, '0', STR_PAD_LEFT ); // sequence number
+               echo PHP_EOL;
+       }
+
+       // Batch Trailer Record
+
+       echo '8'; // Record Type Code
+       echo '200'; // Service Class Code
+       echo str_pad( $count, 6, '0', STR_PAD_LEFT ); // Entry/Addenda Count
+       echo str_pad( substr( $hash, -10 ), 10, '0', STR_PAD_LEFT ); // Entry Hash
+       echo str_pad( number_format( $total, 2, '', '' ), 12, '0', STR_PAD_LEFT ); // Total Debit Entry Dollar Amount
+       echo str_pad( 0, 12, '0', STR_PAD_LEFT ); // Total Credit Entry Dollar Amount
+       echo str_pad( substr( $ach_options['company-id'], 0, 10 ), 10 ); // Company ID
+       echo str_pad( '', 25 ); // Blanks
+       echo str_pad( substr( $ach_options['financial-inst'], 0, 8 ), 8 ); // Originating Financial Institution
+       echo '0000001'; // Batch Number
+       echo PHP_EOL;
+
+
+       // File Trailer Record
+
+       echo '9'; // Record Type Code
+       echo '000001'; // Batch Count
+       echo str_pad( ceil( $count / 10 ), 6, '0', STR_PAD_LEFT ); // Block Count
+       echo str_pad( $count, 8, '0', STR_PAD_LEFT ); // Entry/Addenda Count
+       echo str_pad( substr( $hash, -10 ), 10, '0', STR_PAD_LEFT ); // Entry Hash
+       echo str_pad( number_format( $total, 2, '', '' ), 12, '0', STR_PAD_LEFT ); // Total Debit Entry Dollar Amount
+       echo str_pad( 0, 12, '0', STR_PAD_LEFT ); // Total Credit Entry Dollar Amount
+       echo str_pad( '', 39 ); // Blanks
+       echo PHP_EOL;
+
+       // The file must have a number of lines that is a multiple of 10 (e.g. 10, 20, 30).
+       echo str_repeat( PHP_EOL, 10 - ( ( 4 + $count ) % 10 ) - 1 );
+       return ob_get_clean();
+}
+
+/**
+ * Wires via JP Morgan
+ *
+ * @param array $args
+ *
+ * @return string
+ */
+function _generate_payment_report_jpm_wires( $args ) {
+       $args = wp_parse_args( $args, array(
+               'request_indexes' => array(),
+               'status' => '',
+       ) );
+
+       ob_start();
+       $report = fopen( 'php://output', 'w' );
+
+       // JPM Header
+       fputcsv( $report, array( 'HEADER', gmdate( 'YmdHis' ), '1' ) );
+
+       $total = 0;
+       $count = 0;
+
+       foreach ( $args['request_indexes'] as $index ) {
+               switch_to_blog( $index->blog_id );
+               $post = get_post( $index->post_id );
+
+               if ( $args['status'] && $post->post_status != $args['status'] ) {
+                       restore_current_blog();
+                       continue;
+               }
+
+               // Only wires here.
+               if ( get_post_meta( $post->ID, '_camppayments_payment_method', true ) != 'Wire' ) {
+                       restore_current_blog();
+                       continue;
+               }
+
+               $amount = round( floatval( get_post_meta( $post->ID, '_camppayments_payment_amount', true ) ), 2);
+               $total += $amount;
+               $count += 1;
+
+               // If account starts with two letters, it's most likely an IBAN
+               $account = get_post_meta( $post->ID, '_camppayments_beneficiary_account_number', true );
+               $account = \WCP_Encryption::maybe_decrypt( $account );
+               $account = preg_replace( '#\s#','', $account );
+               $account_type = preg_match( '#^[a-z]{2}#i', $account ) ? 'IBAN' : 'ACCT';
+
+               $row = array(
+                       '1-input-type' => 'P',
+                       '2-payment-method' => 'WIRES',
+                       '3-debit-bank-id' => apply_filters( 'wcb_payment_req_bank_id', '' ), // external file
+                       '4-account-number' => apply_filters( 'wcb_payment_req_bank_number', '' ), // external file
+                       '5-bank-to-bank' => 'N',
+                       '6-txn-currency' => get_post_meta( $post->ID, '_camppayments_currency', true ),
+                       '7-txn-amount' => number_format( $amount, 2, '.', '' ),
+                       '8-equiv-amount' => '',
+                       '9-clearing' => '',
+                       '10-ben-residence' => '',
+                       '11-rate-type' => '',
+                       '12-blank' => '',
+                       '13-value-date' => '',
+
+                       '14-id-type' => $account_type,
+                       '15-id-value' => $account,
+                       '16-ben-name' => substr( \WCP_Encryption::maybe_decrypt(
+                               get_post_meta( $post->ID, '_camppayments_beneficiary_name', true ) ), 0, 35 ),
+                       '17-address-1' => substr( \WCP_Encryption::maybe_decrypt(
+                               get_post_meta( $post->ID, '_camppayments_beneficiary_street_address', true ) ), 0, 35 ),
+                       '18-address-2' => '',
+                       '19-city-state-zip' => substr( sprintf( '%s %s %s',
+                                       \WCP_Encryption::maybe_decrypt( get_post_meta( $post->ID, '_camppayments_beneficiary_city', true ) ),
+                                       \WCP_Encryption::maybe_decrypt( get_post_meta( $post->ID, '_camppayments_beneficiary_state', true ) ),
+                                       \WCP_Encryption::maybe_decrypt( get_post_meta( $post->ID, '_camppayments_beneficiary_zip_code', true ) )
+                               ), 0, 32 ),
+                       '20-blank' => '',
+                       '21-country' => \WCP_Encryption::maybe_decrypt(
+                               get_post_meta( $post->ID, '_camppayments_beneficiary_country_iso3166', true ) ),
+                       '22-blank' => '',
+                       '23-blank' => '',
+
+                       '24-id-type' => 'SWIFT',
+                       '25-id-value' => get_post_meta( $post->ID, '_camppayments_bank_bic', true ),
+                       '26-ben-bank-name' => substr( get_post_meta( $post->ID, '_camppayments_bank_name', true ), 0, 35 ),
+                       '27-ben-bank-address-1' => substr( get_post_meta( $post->ID, '_camppayments_bank_street_address', true ), 0, 35 ),
+                       '28-ben-bank-address-2' => '',
+                       '29-ben-bank-address-3' => substr( sprintf( '%s %s %s',
+                                       get_post_meta( $post->ID, '_camppayments_bank_city', true ),
+                                       get_post_meta( $post->ID, '_camppayments_bank_state', true ),
+                                       get_post_meta( $post->ID, '_camppayments_bank_zip_code', true )
+                                ), 0, 35 ),
+                       '30-ben-bank-country' => get_post_meta( $post->ID, '_camppayments_bank_country_iso3166', true ),
+                       '31-supl-id-type' => '',
+                       '32-supl-id-value' => '',
+
+                       '33-blank' => '',
+                       '34-blank' => '',
+                       '35-blank' => '',
+                       '36-blank' => '',
+                       '37-blank' => '',
+                       '38-blank' => '',
+                       '39-blank' => '',
+
+                       // Filled out later if not empty.
+                       '40-id-type' => '',
+                       '41-id-value' => '',
+                       '42-interm-bank-name' => '',
+                       '43-interm-bank-address-1' => '',
+                       '44-interm-bank-address-2' => '',
+                       '45-interm-bank-address-3' => '',
+                       '46-interm-bank-country' => '',
+                       '47-supl-id-type' => '',
+                       '48-supl-id-value' => '',
+
+                       '49-id-type' => '',
+                       '50-id-value' => '',
+                       '51-party-name' => '',
+                       '52-party-address-1' => '',
+                       '53-party-address-2' => '',
+                       '54-party-address-3' => '',
+                       '55-party-country' => '',
+
+                       '56-blank' => '',
+                       '57-blank' => '',
+                       '58-blank' => '',
+                       '59-blank' => '',
+                       '60-blank' => '',
+                       '61-blank' => '',
+                       '62-blank' => '',
+                       '63-blank' => '',
+                       '64-blank' => '',
+                       '65-blank' => '',
+                       '66-blank' => '',
+                       '67-blank' => '',
+                       '68-blank' => '',
+                       '69-blank' => '',
+                       '70-blank' => '',
+                       '71-blank' => '',
+                       '72-blank' => '',
+                       '73-blank' => '',
+
+                       '74-ref-text' => substr( get_post_meta( $post->ID, '_camppayments_invoice_number', true ), 0, 16 ),
+                       '75-internal-ref' => '',
+                       '76-on-behalf-of' => '',
+
+                       '77-detial-1' => '',
+                       '78-detial-2' => '',
+                       '79-detial-3' => '',
+                       '80-detail-4' => '',
+
+                       '81-blank' => '',
+                       '82-blank' => '',
+                       '83-blank' => '',
+                       '84-blank' => '',
+                       '85-blank' => '',
+                       '86-blank' => '',
+                       '87-blank' => '',
+                       '88-blank' => '',
+
+                       '89-reporting-code' => '',
+                       '90-country' => '',
+                       '91-inst-1' => '',
+                       '92-inst-2' => '',
+                       '93-inst-3' => '',
+                       '94-inst-code-1' => '',
+                       '95-inst-text-1' => '',
+                       '96-inst-code-2' => '',
+                       '97-inst-text-2' => '',
+                       '98-inst-code-3' => '',
+                       '99-inst-text-3' => '',
+
+                       '100-stor-code-1' => '',
+                       '101-stor-line-2' => '', // Hmm?
+                       '102-stor-code-2' => '',
+                       '103-stor-line-2' => '',
+                       '104-stor-code-3' => '',
+                       '105-stor-line-3' => '',
+                       '106-stor-code-4' => '',
+                       '107-stor-line-4' => '',
+                       '108-stor-code-5' => '',
+                       '109-stor-line-5' => '',
+                       '110-stor-code-6' => '',
+                       '111-stor-line-6' => '',
+
+                       '112-priority' => '',
+                       '113-blank' => '',
+                       '114-charges' => '',
+                       '115-blank' => '',
+                       '116-details' => '',
+                       '117-note' => substr( sprintf( 'wcb-%d-%d', $index->blog_id, $index->post_id ), 0, 70 ),
+               );
+
+               // If an intermediary bank is given.
+               $interm_swift = get_post_meta( $post->ID, '_camppayments_interm_bank_swift', true );
+               if ( ! empty( $iterm_swift ) ) {
+                       $row['40-id-type'] = 'SWIFT';
+                       $row['41-id-value'] = $interm_swift;
+
+                       $row['42-interm-bank-name'] = substr( get_post_meta( $post->ID, '_camppayments_interm_bank_name', true ), 0, 35 );
+                       $row['43-interm-bank-address-1'] = substr( get_post_meta( $post->ID, '_camppayments_interm_bank_street_address', true ), 0, 35 );
+
+                       $row['44-interm-bank-address-2'] = '';
+                       $row['45-interm-bank-address-3'] = substr( sprintf( '%s %s %s',
+                               get_post_meta( $post->ID, '_camppayments_interm_bank_city', true ),
+                               get_post_meta( $post->ID, '_camppayments_interm_bank_state', true ),
+                               get_post_meta( $post->ID, '_camppayments_interm_bank_zip_code', true )
+                       ), 0, 32 );
+
+                       $row['46-interm-bank-country'] = get_post_meta( $post->ID, '_camppayments_interm_bank_country_iso3166', true );
+
+                       $row['47-supl-id-type'] = 'ACCT';
+                       $row['48-supl-id-value'] = get_post_meta( $post->ID, '_camppayments_interm_bank_account', true );
+               }
+
+               // Use for debugging.
+               // print_r( $row );
+
+               fputcsv( $report, array_values( $row ) );
+               restore_current_blog();
+       }
+
+       // JPM Trailer
+       fputcsv( $report, array( 'TRAILER', $count, $total ) );
+
+       fclose( $report );
+       $results = ob_get_clean();
+
+       // JPM chokes on accents and non-latin characters.
+       $results = remove_accents( $results );
+       return $results;
+}
+
+/**
+ * Exclude weekends and JPM holidays.
+ *
+ * Needs to be updated every year.
+ *
+ * @return int Timestamp.
+ */
+function _next_business_day_timestamp() {
+       static $timestamp;
+
+       if ( isset( $timestamp ) )
+               return $timestamp;
+
+       $holidays = array(
+               date( 'Ymd', strtotime( 'Friday, January 1, 2016' ) ),
+               date( 'Ymd', strtotime( 'Monday, January 18, 2016' ) ),
+               date( 'Ymd', strtotime( 'Monday, February 15, 2016' ) ),
+               date( 'Ymd', strtotime( 'Monday, May 30, 2016' ) ),
+               date( 'Ymd', strtotime( 'Monday, July 4, 2016' ) ),
+               date( 'Ymd', strtotime( 'Monday, September 5, 2016' ) ),
+               date( 'Ymd', strtotime( 'Friday, November 11, 2016' ) ),
+               date( 'Ymd', strtotime( 'Thursday, November 24, 2016' ) ),
+               date( 'Ymd', strtotime( 'Monday, December 26, 2016' ) ),
+       );
+
+       $timestamp = strtotime( 'today + 1 weekday' );
+       $attempts = 5;
+
+       while ( in_array( date( 'Ymd', $timestamp ), $holidays ) ) {
+               $timestamp = strtotime( '+ 1 weekday', $timestamp );
+               $attempts--;
+
+               if ( ! $attempts )
+                       break;
+       }
+
+       return $timestamp;
+}
+
+/**
</ins><span class="cx" style="display: block; padding: 0 10px">  * Remove the empty Budgets submenu item
</span><span class="cx" style="display: block; padding: 0 10px">  *
</span><span class="cx" style="display: block; padding: 0 10px">  * @todo This may no longer be needed once the Budgets post type and Overview pages are added
</span><span class="cx" style="display: block; padding: 0 10px">  */
</span><span class="cx" style="display: block; padding: 0 10px"> function remove_budgets_submenu() {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        remove_submenu_page( 'wordcamp-budgets-dashboard', 'wordcamp-budgets-dashboard' );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ remove_submenu_page( 'wordcamp-budgets-dashboard', 'wordcamp-budgets-dashboard' );
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><span class="cx" style="display: block; padding: 0 10px">  * Enqueue scripts and styles
</span><span class="cx" style="display: block; padding: 0 10px">  */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-function enqueue_scripts() {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+function enqueue_scripts( $hook ) {
</ins><span class="cx" style="display: block; padding: 0 10px">         wp_enqueue_style(
</span><span class="cx" style="display: block; padding: 0 10px">                'wordcamp-budgets-dashboard',
</span><span class="cx" style="display: block; padding: 0 10px">                plugins_url( 'css/wordcamp-budgets-dashboard.css', __DIR__ ),
</span><span class="cx" style="display: block; padding: 0 10px">                array(),
</span><span class="cx" style="display: block; padding: 0 10px">                1
</span><span class="cx" style="display: block; padding: 0 10px">        );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+       if ( $hook == 'budgets_page_wcb-import-export' ) {
+               wp_enqueue_script( 'jquery-ui-datepicker' );
+               wp_enqueue_style( 'jquery-ui' );
+               wp_enqueue_style( 'wp-datepicker-skins' );
+       }
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -61,7 +1000,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">  */
</span><span class="cx" style="display: block; padding: 0 10px"> function format_amount( $amount, $currency ) {
</span><span class="cx" style="display: block; padding: 0 10px">        $formatted_amount = '';
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        $amount           = \WordCamp_Budgets::validate_amount( $amount );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ $amount = \WordCamp_Budgets::validate_amount( $amount );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        if ( false === strpos( $currency, 'null' ) && $amount ) {
</span><span class="cx" style="display: block; padding: 0 10px">                $formatted_amount = sprintf( '%s&nbsp;%s', number_format( $amount, 2 ), $currency );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -93,8 +1032,8 @@
</span><span class="cx" style="display: block; padding: 0 10px"> function convert_currency( $from, $to, $amount ) {
</span><span class="cx" style="display: block; padding: 0 10px">        global $wpdb;
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        $from      = strtolower( $from );
-       $to        = strtolower( $to );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ $from = strtolower( $from );
+       $to = strtolower( $to );
</ins><span class="cx" style="display: block; padding: 0 10px">         $cache_key = md5( sprintf( 'wcp-exchange-rate-%s:%s', $from, $to ) );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        $rate = 0;
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -106,7 +1045,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                $url = add_query_arg( 'q',   rawurlencode( $wpdb->prepare( 'select * from yahoo.finance.xchange where pair = %s', $from . $to ) ), $url );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $request = wp_remote_get( esc_url_raw( $url ) );
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $body    = json_decode( wp_remote_retrieve_body( $request ), true );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $body = json_decode( wp_remote_retrieve_body( $request ), true );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                if ( ! empty( $body['query']['results']['rate']['Ask'] ) ) {
</span><span class="cx" style="display: block; padding: 0 10px">                        $rate = floatval( $body['query']['results']['rate']['Ask'] );
</span></span></pre>
</div>
</div>

</body>
</html>