Changeset 2694
- Timestamp:
- 03/04/2016 10:34:58 AM (10 years ago)
- Location:
- sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-payments-network/includes
- Files:
-
- 2 edited
-
payment-requests-dashboard.php (modified) (6 diffs)
-
wordcamp-budgets-dashboard.php (modified) (8 diffs)
Legend:
- Unmodified
- Added
- Removed
-
sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-payments-network/includes/payment-requests-dashboard.php
r2668 r2694 17 17 18 18 add_action( 'wordcamp_payments_aggregate', array( __CLASS__, 'aggregate' ) ); 19 add_action( 'admin_enqueue_scripts', array( __CLASS__, 'enqueue_assets' ) );20 19 add_action( 'network_admin_menu', array( __CLASS__, 'network_admin_menu' ) ); 21 20 add_action( 'init', array( __CLASS__, 'upgrade' ) ); 22 add_action( 'init', array( __CLASS__, 'process_export_request' ) );23 21 add_action( 'init', array( __CLASS__, 'process_import_request' ) ); 24 22 … … 239 237 240 238 /** 241 * Enqueue scripts and stylesheets242 *243 * @param string $hook244 */245 public static function enqueue_assets( $hook ) {246 if ( 'index_page_wcp-dashboard' == $hook && 'export' == self::get_current_tab() ) {247 wp_enqueue_script( 'jquery-ui-datepicker' );248 wp_enqueue_style( 'jquery-ui' );249 wp_enqueue_style( 'wp-datepicker-skins' );250 }251 }252 253 /**254 239 * Renders the Dashboard - Payments screen. 255 240 */ … … 266 251 267 252 <?php 268 if ( 'export' == self::get_current_tab() ) { 269 self::render_export_tab(); 270 } elseif ( 'import' == self::get_current_tab() ) { 253 if ( 'import' == self::get_current_tab() ) { 271 254 self::render_import_tab(); 272 255 } … … 381 364 } 382 365 restore_current_blog(); 383 }384 385 /**386 * Get available export options.387 *388 * @return array389 */390 public static function get_export_types() {391 return array(392 'default' => array(393 'label' => 'Default',394 'mime_type' => 'text/csv',395 'callback' => array( __CLASS__, '_generate_payment_report_default' ),396 'filename' => 'wordcamp-payments-%s-%s-default.csv',397 ),398 'jpm_wires' => array(399 'label' => 'JP Morgan Access - Wire Payments',400 'mime_type' => 'text/csv',401 'callback' => array( __CLASS__, '_generate_payment_report_jpm_wires' ),402 'filename' => 'wordcamp-payments-%s-%s-jpm-wires.csv',403 ),404 'jpm_ach' => array(405 'label' => 'JP Morgan - NACHA',406 'mime_type' => 'text/plain',407 'callback' => array( __CLASS__, '_generate_payment_report_jpm_ach' ),408 'filename' => 'wordcamp-payments-%s-%s-jpm-ach.ach',409 ),410 'jpm_checks' => array(411 'label' => 'JP Morgan - Quick Checks',412 'mime_type' => 'text/csv',413 'callback' => array( __CLASS__, '_generate_payment_report_jpm_checks' ),414 'filename' => 'wordcamp-payments-%s-%s-jpm-checks.csv',415 ),416 );417 }418 419 /**420 * Process export requests421 */422 public static function process_export_request() {423 if ( empty( $_POST['submit'] ) || 'export' != self::get_current_tab() ) {424 return;425 }426 427 if ( ! current_user_can( 'manage_network' ) || ! check_admin_referer( 'export', 'wcpn_request_export' ) ) {428 return;429 }430 431 $export_types = self::get_export_types();432 433 if ( array_key_exists( $_POST['wcpn_export_type'], $export_types ) ) {434 $export_type = $export_types[ $_POST['wcpn_export_type'] ];435 } else {436 $export_type = $export_types['default'];437 }438 439 $status = $_POST['wcpn_export_status'];440 if ( ! in_array( $status, array( 'wcb-approved', 'wcb-paid' ) ) )441 $status = 'wcb-approved';442 443 $start_date = strtotime( $_POST['wcpn_export_start_date'] . ' 00:00:00' );444 $end_date = strtotime( $_POST['wcpn_export_end_date'] . ' 23:59:59' );445 $filename = sprintf( $export_type['filename'], date( 'Ymd', $start_date ), date( 'Ymd', $end_date ) );446 $filename = sanitize_file_name( $filename );447 448 $report = self::generate_payment_report( $status, $start_date, $end_date, $export_type );449 450 if ( is_wp_error( $report ) ) {451 add_settings_error( 'wcp-dashboard', $report->get_error_code(), $report->get_error_message() );452 } else {453 header( sprintf( 'Content-Type: %s', $export_type['mime_type'] ) );454 header( sprintf( 'Content-Disposition: attachment; filename="%s"', $filename ) );455 header( 'Cache-control: private' );456 header( 'Pragma: private' );457 header( 'Expires: Mon, 26 Jul 1997 05:00:00 GMT' );458 459 echo $report;460 die();461 }462 }463 464 /*465 * Generate and return the raw payment report contents466 *467 * @param string $date_type 'paid' | 'created'468 * @param int $start_date469 * @param int $end_date470 * @param string $type471 *472 * @return string | WP_Error473 */474 protected static function generate_payment_report( $status, $start_date, $end_date, $export_type ) {475 global $wpdb;476 477 if ( ! is_int( $start_date ) || ! is_int( $end_date ) ) {478 return new WP_Error( 'wcpn_bad_dates', 'Invalid start or end date.' );479 }480 481 $table_name = self::get_table_name();482 $date_type = 'updated';483 484 if ( $status == 'wcb-paid' )485 $date_type = 'paid';486 487 $request_indexes = $wpdb->get_results( $wpdb->prepare( "488 SELECT *489 FROM `{$table_name}`490 WHERE `{$date_type}` BETWEEN %d AND %d",491 $start_date,492 $end_date493 ) );494 495 if ( ! is_callable( $export_type['callback'] ) )496 return new WP_Error( 'wcpn_invalid_type', 'The export type is invalid.' );497 498 $args = array(499 'request_indexes' => $request_indexes,500 'start_date' => $start_date,501 'end_date' => $end_date,502 'export_type' => $export_type,503 'status' => $status,504 );505 506 return call_user_func( $export_type['callback'], $args );507 }508 509 /**510 * Default CSV report511 *512 * @param array $args513 *514 * @return string515 */516 protected static function _generate_payment_report_default( $args ) {517 $args = wp_parse_args( $args, array(518 'request_indexes' => array(),519 'status' => '',520 ) );521 522 $column_headings = array(523 'WordCamp', 'ID', 'Title', 'Status', 'Date Vendor was Paid', 'Creation Date', 'Due Date', 'Amount',524 'Currency', 'Category', 'Payment Method','Vendor Name', 'Vendor Contact Person', 'Vendor Country',525 'Check Payable To', 'URL', 'Supporting Documentation Notes',526 );527 528 ob_start();529 $report = fopen( 'php://output', 'w' );530 531 fputcsv( $report, $column_headings );532 533 foreach( $args['request_indexes'] as $index ) {534 $row = self::get_report_row( $index, $args );535 if ( ! empty( $row ) ) {536 fputcsv( $report, $row );537 }538 }539 540 fclose( $report );541 return ob_get_clean();542 }543 544 /**545 * Quick Checks via JP Morgan546 *547 * @param array $args548 *549 * @return string550 */551 protected static function _generate_payment_report_jpm_checks( $args ) {552 $args = wp_parse_args( $args, array(553 'request_indexes' => array(),554 'status' => '',555 ) );556 557 $options = apply_filters( 'wcb_payment_req_check_options', array(558 'pws_customer_id' => '',559 'account_number' => '',560 'contact_email' => '',561 'contact_phone' => '',562 ) );563 564 $report = fopen( 'php://output', 'w' );565 ob_start();566 567 // File Header568 fputcsv( $report, array( 'FILHDR', 'PWS', $options['pws_customer_id'], date( 'm/d/Y' ), date( 'Hi' ) ), ',', '|' );569 570 $total = 0;571 $count = 0;572 573 if ( false !== get_site_transient( '_wcb_jpm_checks_counter_lock' ) ) {574 wp_die( 'JPM Checks Export is locked. Please try again later or contact support.' );575 }576 577 // Avoid at least *some* race conditions.578 set_site_transient( '_wcb_jpm_checks_counter_lock', 1, 30 );579 $start = absint( get_site_option( '_wcb_jpm_checks_counter', 0 ) );580 581 foreach ( $args['request_indexes'] as $index ) {582 switch_to_blog( $index->blog_id );583 $post = get_post( $index->post_id );584 585 if ( $args['status'] && $post->post_status != $args['status'] )586 continue;587 588 if ( get_post_meta( $post->ID, '_camppayments_payment_method', true ) != 'Check' )589 continue;590 591 $count++;592 $amount = round( floatval( get_post_meta( $post->ID, '_camppayments_payment_amount', true ) ), 2 );593 $total += $amount;594 595 $payable_to = WCP_Encryption::maybe_decrypt( get_post_meta( $post->ID, '_camppayments_payable_to', true ) );596 $payable_to = html_entity_decode( $payable_to ); // J&J to J&J597 $countries = WordCamp_Budgets::get_valid_countries_iso3166();598 $vendor_country_code = get_post_meta( $post->ID, '_camppayments_vendor_country_iso3166', true );599 if ( ! empty( $countries[ $vendor_country_code ] ) ) {600 $vendor_country_code = $countries[ $vendor_country_code ]['alpha3'];601 }602 603 $description = sanitize_text_field( get_post_meta( $post->ID, '_camppayments_description', true ) );604 $description = html_entity_decode( $description );605 $invoice_number = get_post_meta( $post->ID, '_camppayments_invoice_number', true );606 if ( ! empty( $invoice_number ) ) {607 $description = sprintf( 'Invoice %s. %s', $invoice_number, $description );608 }609 610 // Payment Header611 fputcsv( $report, array(612 'PMTHDR',613 'USPS',614 'QKCHECKS',615 date( 'm/d/Y' ),616 number_format( $amount, 2, '.', '' ),617 $options['account_number'],618 $start + $count, // must be globally unique?619 $options['contact_email'],620 $options['contact_phone'],621 ), ',', '|' );622 623 // Payee Name Record624 fputcsv( $report, array(625 'PAYENM',626 substr( $payable_to, 0, 35 ),627 '',628 sprintf( '%d-%d', $index->blog_id, $index->post_id ),629 ), ',', '|' );630 631 // Payee Address Record632 fputcsv( $report, array(633 'PYEADD',634 substr( get_post_meta( $post->ID, '_camppayments_vendor_street_address', true ), 0, 35 ),635 '',636 ), ',', '|' );637 638 // Additional Payee Address Record639 fputcsv( $report, array( 'ADDPYE', '', '' ), ',', '|' );640 641 // Payee Postal Record642 fputcsv( $report, array(643 'PYEPOS',644 substr( get_post_meta( $post->ID, '_camppayments_vendor_city', true ), 0, 35 ),645 substr( get_post_meta( $post->ID, '_camppayments_vendor_state', true ), 0, 35 ),646 substr( get_post_meta( $post->ID, '_camppayments_vendor_zip_code', true ), 0, 10 ),647 substr( $vendor_country_code, 0, 3 ),648 ), ',', '|' );649 650 // Payment Description651 fputcsv( $report, array(652 'PYTDES',653 substr( $description, 0, 122 ),654 ), ',', '|' );655 656 restore_current_blog();657 }658 659 // File Trailer660 fputcsv( $report, array( 'FILTRL', $count * 6 + 2 ), ',', '|' );661 662 // Update counter and unlock663 $start = absint( get_site_option( '_wcb_jpm_checks_counter', 0 ) );664 update_site_option( '_wcb_jpm_checks_counter', $start + $count );665 delete_site_transient( '_wcb_jpm_checks_counter_lock' );666 667 fclose( $report );668 return ob_get_clean();669 }670 671 /**672 * NACHA via JP Morgan673 *674 * @param array $args675 *676 * @return string677 */678 protected static function _generate_payment_report_jpm_ach( $args ) {679 $args = wp_parse_args( $args, array(680 'request_indexes' => array(),681 'status' => '',682 ) );683 684 $ach_options = apply_filters( 'wcb_payment_req_ach_options', array(685 'bank-routing-number' => '', // Immediate Destination (bank routing number)686 'company-id' => '', // Company ID687 'financial-inst' => '', // Originating Financial Institution688 ) );689 690 ob_start();691 692 // File Header Record693 694 echo '1'; // Record Type Code695 echo '01'; // Priority Code696 echo ' ' . str_pad( substr( $ach_options['bank-routing-number'], 0, 9 ), 9, '0', STR_PAD_LEFT );697 echo str_pad( substr( $ach_options['company-id'], 0, 10 ), 10, '0', STR_PAD_LEFT ); // Immediate Origin (TIN)698 echo date( 'ymd' ); // Transmission Date699 echo date( 'Hi' ); // Transmission Time700 echo 'A'; // File ID Modifier701 echo '094'; // Record Size702 echo '10'; // Blocking Factor703 echo '1'; // Format Code704 echo str_pad( 'JPMORGANCHASE', 23 ); // Destination705 echo str_pad( 'WCEXPORT', 23 ); // Origin706 echo str_pad( '', 8 ); // Reference Code (optional)707 echo PHP_EOL;708 709 // Batch Header Record710 711 echo '5'; // Record Type Code712 echo '200'; // Service Type Code713 echo 'WordCamp Communi'; // Company Name714 echo str_pad( '', 20 ); // Blanks715 echo str_pad( substr( $ach_options['company-id'], 0, 10 ), 10 ); // Company Identification716 717 // Get the first one in the set.718 // @todo Split batches by account type.719 foreach ( $args['request_indexes'] as $index ) {720 switch_to_blog( $index->blog_id );721 $post = get_post( $index->post_id );722 $account_type = get_post_meta( $post->ID, '_camppayments_ach_account_type', true );723 restore_current_blog();724 725 break;726 }727 728 $entry_class = $account_type == 'Personal' ? 'PPD' : 'CCD';729 echo $entry_class; // Standard Entry Class730 731 echo 'Vendor Pay'; // Entry Description732 echo date( 'ymd', self::_next_business_day_timestamp() ); // Company Description Date733 echo date( 'ymd', self::_next_business_day_timestamp() ); // Effective Entry Date734 echo str_pad( '', 3 ); // Blanks735 echo '1'; // Originator Status Code736 echo str_pad( substr( $ach_options['financial-inst'], 0, 8 ), 8 ); // Originating Financial Institution737 echo '0000001'; // Batch Number738 echo PHP_EOL;739 740 $count = 0;741 $total = 0;742 $hash = 0;743 744 foreach ( $args['request_indexes'] as $index ) {745 switch_to_blog( $index->blog_id );746 $post = get_post( $index->post_id );747 748 if ( $args['status'] && $post->post_status != $args['status'] )749 continue;750 751 if ( get_post_meta( $post->ID, '_camppayments_payment_method', true ) != 'Direct Deposit' )752 continue;753 754 $count++;755 756 // Entry Detail Record757 758 echo '6'; // Record Type Code759 760 $transaction_code = $account_type == 'Personal' ? '27' : '22';761 echo $transaction_code; // Transaction Code762 763 // Transit/Routing Number of Destination Bank + Check digit764 $routing_number = get_post_meta( $post->ID, '_camppayments_ach_routing_number', true );765 $routing_number = WCP_Encryption::maybe_decrypt( $routing_number );766 $routing_number = substr( $routing_number, 0, 8 + 1 );767 $routing_number = str_pad( $routing_number, 8 + 1 );768 $hash += absint( substr( $routing_number, 0, 8 ) );769 echo $routing_number;770 771 // Bank Account Number772 $account_number = get_post_meta( $post->ID, '_camppayments_ach_account_number', true );773 $account_number = WCP_Encryption::maybe_decrypt( $account_number );774 $account_number = substr( $account_number, 0, 17 );775 $account_number = str_pad( $account_number, 17 );776 echo $account_number;777 778 // Amount779 $amount = round( floatval( get_post_meta( $post->ID, '_camppayments_payment_amount', true ) ), 2 );780 $total += $amount;781 $amount = str_pad( number_format( $amount, 2, '', '' ), 10, '0', STR_PAD_LEFT );782 echo $amount;783 784 // Individual Identification Number785 echo str_pad( sprintf( '%d-%d', $index->blog_id, $index->post_id ), 15 );786 787 // Individual Name788 $name = get_post_meta( $post->ID, '_camppayments_ach_account_holder_name', true );789 $name = WCP_Encryption::maybe_decrypt( $name );790 $name = substr( $name, 0, 22 );791 $name = str_pad( $name, 22 );792 echo $name;793 794 echo ' '; // User Defined Data795 echo '0'; // Addenda Record Indicator796 797 // Trace Number798 echo str_pad( substr( $ach_options['bank-routing-number'], 0, 8 ), 8, '0', STR_PAD_LEFT ); // routing number799 echo str_pad( $count, 7, '0', STR_PAD_LEFT ); // sequence number800 echo PHP_EOL;801 }802 803 // Batch Trailer Record804 805 echo '8'; // Record Type Code806 echo '200'; // Service Class Code807 echo str_pad( $count, 6, '0', STR_PAD_LEFT ); // Entry/Addenda Count808 echo str_pad( substr( $hash, -10 ), 10, '0', STR_PAD_LEFT ); // Entry Hash809 echo str_pad( number_format( $total, 2, '', '' ), 12, '0', STR_PAD_LEFT ); // Total Debit Entry Dollar Amount810 echo str_pad( 0, 12, '0', STR_PAD_LEFT ); // Total Credit Entry Dollar Amount811 echo str_pad( substr( $ach_options['company-id'], 0, 10 ), 10 ); // Company ID812 echo str_pad( '', 25 ); // Blanks813 echo str_pad( substr( $ach_options['financial-inst'], 0, 8 ), 8 ); // Originating Financial Institution814 echo '0000001'; // Batch Number815 echo PHP_EOL;816 817 818 // File Trailer Record819 820 echo '9'; // Record Type Code821 echo '000001'; // Batch Count822 echo str_pad( ceil( $count / 10 ), 6, '0', STR_PAD_LEFT ); // Block Count823 echo str_pad( $count, 8, '0', STR_PAD_LEFT ); // Entry/Addenda Count824 echo str_pad( substr( $hash, -10 ), 10, '0', STR_PAD_LEFT ); // Entry Hash825 echo str_pad( number_format( $total, 2, '', '' ), 12, '0', STR_PAD_LEFT ); // Total Debit Entry Dollar Amount826 echo str_pad( 0, 12, '0', STR_PAD_LEFT ); // Total Credit Entry Dollar Amount827 echo str_pad( '', 39 ); // Blanks828 echo PHP_EOL;829 830 // The file must have a number of lines that is a multiple of 10 (e.g. 10, 20, 30).831 echo str_repeat( PHP_EOL, 10 - ( ( 4 + $count ) % 10 ) - 1 );832 return ob_get_clean();833 }834 835 /**836 * Exclude weekends and JPM holidays.837 *838 * Needs to be updated every year.839 *840 * @return int Timestamp.841 */842 private static function _next_business_day_timestamp() {843 static $timestamp;844 845 if ( isset( $timestamp ) )846 return $timestamp;847 848 $holidays = array(849 date( 'Ymd', strtotime( 'Friday, January 1, 2016' ) ),850 date( 'Ymd', strtotime( 'Monday, January 18, 2016' ) ),851 date( 'Ymd', strtotime( 'Monday, February 15, 2016' ) ),852 date( 'Ymd', strtotime( 'Monday, May 30, 2016' ) ),853 date( 'Ymd', strtotime( 'Monday, July 4, 2016' ) ),854 date( 'Ymd', strtotime( 'Monday, September 5, 2016' ) ),855 date( 'Ymd', strtotime( 'Friday, November 11, 2016' ) ),856 date( 'Ymd', strtotime( 'Thursday, November 24, 2016' ) ),857 date( 'Ymd', strtotime( 'Monday, December 26, 2016' ) ),858 );859 860 $timestamp = strtotime( 'today + 1 weekday' );861 $attempts = 5;862 863 while ( in_array( date( 'Ymd', $timestamp ), $holidays ) ) {864 $timestamp = strtotime( '+ 1 weekday', $timestamp );865 $attempts--;866 867 if ( ! $attempts )868 break;869 }870 871 return $timestamp;872 }873 874 /**875 * Wires via JP Morgan876 *877 * @param array $args878 *879 * @return string880 */881 protected static function _generate_payment_report_jpm_wires( $args ) {882 $args = wp_parse_args( $args, array(883 'request_indexes' => array(),884 'status' => '',885 ) );886 887 ob_start();888 $report = fopen( 'php://output', 'w' );889 890 // JPM Header891 fputcsv( $report, array( 'HEADER', gmdate( 'YmdHis' ), '1' ) );892 893 $total = 0;894 $count = 0;895 896 foreach ( $args['request_indexes'] as $index ) {897 switch_to_blog( $index->blog_id );898 $post = get_post( $index->post_id );899 900 if ( $args['status'] && $post->post_status != $args['status'] )901 continue;902 903 // Only wires here.904 if ( get_post_meta( $post->ID, '_camppayments_payment_method', true ) != 'Wire' )905 continue;906 907 $amount = round( floatval( get_post_meta( $post->ID, '_camppayments_payment_amount', true ) ), 2);908 $total += $amount;909 $count += 1;910 911 // If account starts with two letters, it's most likely an IBAN912 $account = get_post_meta( $post->ID, '_camppayments_beneficiary_account_number', true );913 $account = WCP_Encryption::maybe_decrypt( $account );914 $account = preg_replace( '#\s#','', $account );915 $account_type = preg_match( '#^[a-z]{2}#i', $account ) ? 'IBAN' : 'ACCT';916 917 $row = array(918 '1-input-type' => 'P',919 '2-payment-method' => 'WIRES',920 '3-debit-bank-id' => apply_filters( 'wcb_payment_req_bank_id', '' ), // external file921 '4-account-number' => apply_filters( 'wcb_payment_req_bank_number', '' ), // external file922 '5-bank-to-bank' => 'N',923 '6-txn-currency' => get_post_meta( $post->ID, '_camppayments_currency', true ),924 '7-txn-amount' => $amount,925 '8-equiv-amount' => '',926 '9-clearing' => '',927 '10-ben-residence' => '',928 '11-rate-type' => '',929 '12-blank' => '',930 '13-value-date' => '',931 932 '14-id-type' => $account_type,933 '15-id-value' => $account,934 '16-ben-name' => substr( WCP_Encryption::maybe_decrypt(935 get_post_meta( $post->ID, '_camppayments_beneficiary_name', true ) ), 0, 35 ),936 '17-address-1' => substr( WCP_Encryption::maybe_decrypt(937 get_post_meta( $post->ID, '_camppayments_beneficiary_street_address', true ) ), 0, 35 ),938 '18-address-2' => '',939 '19-city-state-zip' => substr( sprintf( '%s %s %s',940 WCP_Encryption::maybe_decrypt( get_post_meta( $post->ID, '_camppayments_beneficiary_city', true ) ),941 WCP_Encryption::maybe_decrypt( get_post_meta( $post->ID, '_camppayments_beneficiary_state', true ) ),942 WCP_Encryption::maybe_decrypt( get_post_meta( $post->ID, '_camppayments_beneficiary_zip_code', true ) )943 ), 0, 32 ),944 '20-blank' => '',945 '21-country' => WCP_Encryption::maybe_decrypt(946 get_post_meta( $post->ID, '_camppayments_beneficiary_country_iso3166', true ) ),947 '22-blank' => '',948 '23-blank' => '',949 950 '24-id-type' => 'SWIFT',951 '25-id-value' => get_post_meta( $post->ID, '_camppayments_bank_bic', true ),952 '26-ben-bank-name' => substr( get_post_meta( $post->ID, '_camppayments_bank_name', true ), 0, 35 ),953 '27-ben-bank-address-1' => substr( get_post_meta( $post->ID, '_camppayments_bank_street_address', true ), 0, 35 ),954 '28-ben-bank-address-2' => '',955 '29-ben-bank-address-3' => substr( sprintf( '%s %s %s',956 get_post_meta( $post->ID, '_camppayments_bank_city', true ),957 get_post_meta( $post->ID, '_camppayments_bank_state', true ),958 get_post_meta( $post->ID, '_camppayments_bank_zip_code', true )959 ), 0, 35 ),960 '30-ben-bank-country' => get_post_meta( $post->ID, '_camppayments_bank_country_iso3166', true ),961 '31-supl-id-type' => '',962 '32-supl-id-value' => '',963 964 '33-blank' => '',965 '34-blank' => '',966 '35-blank' => '',967 '36-blank' => '',968 '37-blank' => '',969 '38-blank' => '',970 '39-blank' => '',971 972 // Filled out later if not empty.973 '40-id-type' => '',974 '41-id-value' => '',975 '42-interm-bank-name' => '',976 '43-interm-bank-address-1' => '',977 '44-interm-bank-address-2' => '',978 '45-interm-bank-address-3' => '',979 '46-interm-bank-country' => '',980 '47-supl-id-type' => '',981 '48-supl-id-value' => '',982 983 '49-id-type' => '',984 '50-id-value' => '',985 '51-party-name' => '',986 '52-party-address-1' => '',987 '53-party-address-2' => '',988 '54-party-address-3' => '',989 '55-party-country' => '',990 991 '56-blank' => '',992 '57-blank' => '',993 '58-blank' => '',994 '59-blank' => '',995 '60-blank' => '',996 '61-blank' => '',997 '62-blank' => '',998 '63-blank' => '',999 '64-blank' => '',1000 '65-blank' => '',1001 '66-blank' => '',1002 '67-blank' => '',1003 '68-blank' => '',1004 '69-blank' => '',1005 '70-blank' => '',1006 '71-blank' => '',1007 '72-blank' => '',1008 '73-blank' => '',1009 1010 '74-ref-text' => substr( get_post_meta( $post->ID, '_camppayments_invoice_number', true ), 0, 16 ),1011 '75-internal-ref' => '',1012 '76-on-behalf-of' => '',1013 1014 '77-detial-1' => '',1015 '78-detial-2' => '',1016 '79-detial-3' => '',1017 '80-detail-4' => '',1018 1019 '81-blank' => '',1020 '82-blank' => '',1021 '83-blank' => '',1022 '84-blank' => '',1023 '85-blank' => '',1024 '86-blank' => '',1025 '87-blank' => '',1026 '88-blank' => '',1027 1028 '89-reporting-code' => '',1029 '90-country' => '',1030 '91-inst-1' => '',1031 '92-inst-2' => '',1032 '93-inst-3' => '',1033 '94-inst-code-1' => '',1034 '95-inst-text-1' => '',1035 '96-inst-code-2' => '',1036 '97-inst-text-2' => '',1037 '98-inst-code-3' => '',1038 '99-inst-text-3' => '',1039 1040 '100-stor-code-1' => '',1041 '101-stor-line-2' => '', // Hmm?1042 '102-stor-code-2' => '',1043 '103-stor-line-2' => '',1044 '104-stor-code-3' => '',1045 '105-stor-line-3' => '',1046 '106-stor-code-4' => '',1047 '107-stor-line-4' => '',1048 '108-stor-code-5' => '',1049 '109-stor-line-5' => '',1050 '110-stor-code-6' => '',1051 '111-stor-line-6' => '',1052 1053 '112-priority' => '',1054 '113-blank' => '',1055 '114-charges' => '',1056 '115-blank' => '',1057 '116-details' => '',1058 '117-note' => substr( sprintf( 'wcb-%d-%d', $index->blog_id, $index->post_id ), 0, 70 ),1059 );1060 1061 // If an intermediary bank is given.1062 $interm_swift = get_post_meta( $post->ID, '_camppayments_interm_bank_swift', true );1063 if ( ! empty( $iterm_swift ) ) {1064 $row['40-id-type'] = 'SWIFT';1065 $row['41-id-value'] = $interm_swift;1066 1067 $row['42-interm-bank-name'] = substr( get_post_meta( $post->ID, '_camppayments_interm_bank_name', true ), 0, 35 );1068 $row['43-interm-bank-address-1'] = substr( get_post_meta( $post->ID, '_camppayments_interm_bank_street_address', true ), 0, 35 );1069 1070 $row['44-interm-bank-address-2'] = '';1071 $row['45-interm-bank-address-3'] = substr( sprintf( '%s %s %s',1072 get_post_meta( $post->ID, '_camppayments_interm_bank_city', true ),1073 get_post_meta( $post->ID, '_camppayments_interm_bank_state', true ),1074 get_post_meta( $post->ID, '_camppayments_interm_bank_zip_code', true )1075 ), 0, 32 );1076 1077 $row['46-interm-bank-country'] = get_post_meta( $post->ID, '_camppayments_interm_bank_country_iso3166', true );1078 1079 $row['47-supl-id-type'] = 'ACCT';1080 $row['48-supl-id-value'] = get_post_meta( $post->ID, '_camppayments_interm_bank_account', true );1081 }1082 1083 // Because CSV is stupid:1084 // print_r( $row );1085 1086 fputcsv( $report, array_values( $row ) );1087 restore_current_blog();1088 }1089 1090 // JPM Trailer1091 fputcsv( $report, array( 'TRAILER', $count, $total ) );1092 1093 fclose( $report );1094 $results = ob_get_clean();1095 1096 // JPM chokes on accents and non-latin characters.1097 $results = remove_accents( $results );1098 return $results;1099 }1100 1101 /**1102 * Gather all the request details needed for a row in the export file1103 *1104 * @param stdClass $index1105 * @param array $args1106 *1107 * @return array1108 */1109 protected static function get_report_row( $index, $args ) {1110 switch_to_blog( $index->blog_id );1111 1112 $request = get_post( $index->post_id );1113 1114 $back_compat_statuses = array(1115 'unpaid' => 'draft',1116 'incomplete' => 'wcb-incomplete',1117 'paid' => 'wcb-paid',1118 );1119 1120 // Map old statuses to new statuses.1121 if ( array_key_exists( $request->post_status, $back_compat_statuses ) ) {1122 $request->post_status = $back_compat_statuses[ $request->post_status ];1123 }1124 1125 if ( $args['status'] && $request->post_status != $args['status'] ) {1126 return null;1127 }1128 1129 $currency = get_post_meta( $index->post_id, '_camppayments_currency', true );1130 $category = get_post_meta( $index->post_id, '_camppayments_payment_category', true );1131 $date_vendor_paid = get_post_meta( $index->post_id, '_camppayments_date_vendor_paid', true );1132 1133 if ( $date_vendor_paid ) {1134 $date_vendor_paid = date( 'Y-m-d', $date_vendor_paid );1135 }1136 1137 if ( 'null-select-one' === $currency ) {1138 $currency = '';1139 }1140 1141 if ( 'null' === $category ) {1142 $category = '';1143 }1144 1145 $row = array(1146 get_wordcamp_name(),1147 sprintf( '%d-%d', $index->blog_id, $index->post_id ),1148 $request->post_title,1149 $index->status,1150 $date_vendor_paid,1151 date( 'Y-m-d', $index->created ),1152 date( 'Y-m-d', $index->due ),1153 get_post_meta( $index->post_id, '_camppayments_payment_amount', true ),1154 $currency,1155 $category,1156 get_post_meta( $index->post_id, '_camppayments_payment_method', true ),1157 get_post_meta( $index->post_id, '_camppayments_vendor_name', true ),1158 get_post_meta( $index->post_id, '_camppayments_vendor_contact_person', true ),1159 get_post_meta( $index->post_id, '_camppayments_vendor_country', true ),1160 WCP_Encryption::maybe_decrypt( get_post_meta( $index->post_id, '_camppayments_payable_to', true ) ),1161 get_edit_post_link( $index->post_id ),1162 get_post_meta( $index->post_id, '_camppayments_file_notes', true ),1163 );1164 1165 restore_current_blog();1166 1167 return $row;1168 }1169 1170 /**1171 * Render the Export tab1172 */1173 protected static function render_export_tab() {1174 $today = date( 'Y-m-d' );1175 $last_month = date( 'Y-m-d', strtotime( 'now - 1 month' ) );1176 ?>1177 1178 <script>1179 /**1180 * Fallback to the jQueryUI datepicker if the browser doesn't support <input type="date">1181 */1182 jQuery( document ).ready( function( $ ) {1183 var browserTest = document.createElement( 'input' );1184 browserTest.setAttribute( 'type', 'date' );1185 1186 if ( 'text' === browserTest.type ) {1187 $( '#wcpn_export' ).find( 'input[type=date]' ).datepicker( {1188 dateFormat : 'yy-mm-dd',1189 changeMonth: true,1190 changeYear : true1191 } );1192 }1193 } );1194 </script>1195 1196 <form id="wcpn_export" method="POST">1197 <?php wp_nonce_field( 'export', 'wcpn_request_export' ); ?>1198 1199 <h2>Export Settings</h2>1200 1201 <table class="form-table">1202 <tr>1203 <th><label>Status</label></th>1204 <td>1205 <select name="wcpn_export_status">1206 <option value="wcb-approved"><?php _e( 'Approved', 'wordcamporg' ); ?></option>1207 <option value="wcb-paid"><?php _e( 'Paid', 'wordcamporg' ); ?></option>1208 </select>1209 </td>1210 </tr>1211 <tr>1212 <th><label>Date Range</label></th>1213 <td>1214 <input type="date" name="wcpn_export_start_date" class="medium-text" value="<?php echo esc_attr( $last_month ); ?>" /> to1215 <input type="date" name="wcpn_export_end_date" class="medium-text" value="<?php echo esc_attr( $today ); ?>" />1216 </td>1217 </tr>1218 <tr>1219 <th><label>Format</label></th>1220 <td>1221 <select name="wcpn_export_type">1222 <?php foreach ( self::get_export_types() as $key => $export_type ) : ?>1223 <option value="<?php echo esc_attr( $key ); ?>"><?php echo esc_html( $export_type['label'] ); ?></option>1224 <?php endforeach; ?>1225 </select>1226 </td>1227 </tr>1228 </table>1229 1230 <?php submit_button( 'Download Export' ); ?>1231 </form>1232 1233 <?php1234 366 } 1235 367 … … 1398 530 'incomplete', 1399 531 1400 'export',1401 532 'import', 1402 533 ); … … 1423 554 'cancelled-failed' => __( 'Cancelled/Failed', 'wordcamporg' ), 1424 555 'incomplete' => __( 'Incomplete', 'wordcamporg' ), 1425 'export' => __( 'Export', 'wordcamporg' ),1426 556 'import' => __( 'Import', 'wordcamporg' ), 1427 557 ); -
sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-payments-network/includes/wordcamp-budgets-dashboard.php
r2535 r2694 8 8 */ 9 9 10 add_action( 'network_admin_menu', __NAMESPACE__ . '\register_budgets_menu' ); 11 add_action( 'network_admin_menu', __NAMESPACE__ . '\remove_budgets_submenu', 11 ); // after other modules have registered their submenu pages 12 add_action( 'admin_enqueue_scripts', __NAMESPACE__ . '\enqueue_scripts' ); 10 add_action( 'network_admin_menu', __NAMESPACE__ . '\register_budgets_menu' ); 11 add_action( 'network_admin_menu', __NAMESPACE__ . '\remove_budgets_submenu', 11 ); // after other modules have registered their submenu pages 12 add_action( 'network_admin_menu', __NAMESPACE__ . '\import_export_admin_menu', 11 ); 13 add_action( 'admin_enqueue_scripts', __NAMESPACE__ . '\enqueue_scripts', 10, 1 ); 14 15 add_action( 'admin_init', __NAMESPACE__ . '\process_export_request' ); 13 16 14 17 /** … … 32 35 33 36 /** 37 * Register the Import/Export dashboard menu item. 38 */ 39 function import_export_admin_menu() { 40 add_submenu_page( 41 'wordcamp-budgets-dashboard', 42 'WordCamp Budgets Import/Export', 43 'Import/Export', 44 'manage_network', 45 'wcb-import-export', 46 __NAMESPACE__ . '\render_import_export' 47 ); 48 } 49 50 /** 51 * Render the import/export screen. 52 */ 53 function render_import_export() { 54 $current_tab = 'import'; 55 if ( ! empty( $_GET['section'] ) && in_array( $_GET['section'], array( 'import', 'export' ) ) ) { 56 $current_tab = $_GET['section']; 57 } 58 59 ?> 60 <div class="wrap"> 61 <h1>Import/Export</h1> 62 63 <?php do_action( 'admin_notices' ); ?> 64 <?php settings_errors(); ?> 65 66 <h3 class="nav-tab-wrapper"> 67 <a class="nav-tab <?php if ( $current_tab == 'import' ) { echo 'nav-tab-active'; } ?>" 68 href="<?php echo add_query_arg( array( 69 'page' => 'wcb-import-export', 70 'section' => 'import', 71 ), network_admin_url( 'admin.php' ) ); ?>">Import</a> 72 73 <a class="nav-tab <?php if ( $current_tab == 'export' ) { echo 'nav-tab-active'; } ?>" 74 href="<?php echo add_query_arg( array( 75 'page' => 'wcb-import-export', 76 'section' => 'export', 77 ), network_admin_url( 'admin.php' ) ); ?>">Export</a> 78 </h3> 79 80 <?php 81 if ( 'export' == $current_tab ) { 82 render_export_tab(); 83 } elseif ( 'import' == $current_tab ) { 84 render_import_tab(); 85 } 86 ?> 87 88 </div> <!-- /wrap --> 89 <?php 90 } 91 92 /** 93 * Get available export options. 94 * 95 * @return array 96 */ 97 function get_export_types() { 98 return array( 99 'default' => array( 100 'label' => 'Regular CSV', 101 'mime_type' => 'text/csv', 102 'callback' => __NAMESPACE__ . '\_generate_payment_report_default', 103 'filename' => 'wordcamp-payments-%s-%s-default.csv', 104 ), 105 'jpm_wires' => array( 106 'label' => 'JP Morgan Access - Wire Payments', 107 'mime_type' => 'text/csv', 108 'callback' => __NAMESPACE__ . '\_generate_payment_report_jpm_wires', 109 'filename' => 'wordcamp-payments-%s-%s-jpm-wires.csv', 110 ), 111 'jpm_ach' => array( 112 'label' => 'JP Morgan - NACHA', 113 'mime_type' => 'text/plain', 114 'callback' => __NAMESPACE__ . '\_generate_payment_report_jpm_ach', 115 'filename' => 'wordcamp-payments-%s-%s-jpm-ach.ach', 116 ), 117 'jpm_checks' => array( 118 'label' => 'JP Morgan - Quick Checks', 119 'mime_type' => 'text/csv', 120 'callback' => __NAMESPACE__ . '\_generate_payment_report_jpm_checks', 121 'filename' => 'wordcamp-payments-%s-%s-jpm-checks.csv', 122 ), 123 ); 124 } 125 126 /** 127 * Render the Import tab 128 */ 129 function render_import_tab() { 130 echo '<p>Move along, nothing to see here.</p>'; 131 } 132 133 /** 134 * Render the export tab 135 */ 136 function render_export_tab() { 137 $today = date( 'Y-m-d' ); 138 $last_month = date( 'Y-m-d', strtotime( 'now - 1 month' ) ); 139 ?> 140 <script> 141 /** 142 * Fallback to the jQueryUI datepicker if the browser doesn't support <input type="date"> 143 */ 144 jQuery( document ).ready( function( $ ) { 145 var browserTest = document.createElement( 'input' ); 146 browserTest.setAttribute( 'type', 'date' ); 147 148 if ( 'text' === browserTest.type ) { 149 $( '#wcpn_export' ).find( 'input[type=date]' ).datepicker( { 150 dateFormat : 'yy-mm-dd', 151 changeMonth: true, 152 changeYear : true 153 } ); 154 } 155 } ); 156 </script> 157 158 <form id="wcpn_export" method="POST"> 159 <?php wp_nonce_field( 'export', 'wcb-request-export' ); ?> 160 161 <h2>Export Settings</h2> 162 163 <table class="form-table"> 164 <tr> 165 <th>Types</th> 166 <td> 167 <label><input type="checkbox" name="wcb-export-types-vendor-payments" 168 value="1" checked disabled /> Vendor Payments</label><br /> 169 <label><input type="checkbox" name="wcb-export-types-reimbursements" 170 value="1" disabled /> Reimbursements</label> 171 </td> 172 <tr> 173 <th>Status</th> 174 <td> 175 <select name="wcb-export-status"> 176 <option value="wcb-approved"><?php _e( 'Approved', 'wordcamporg' ); ?></option> 177 <option value="wcb-paid"><?php _e( 'Paid', 'wordcamporg' ); ?></option> 178 </select> 179 </td> 180 </tr> 181 <tr> 182 <th>Date Range</th> 183 <td> 184 <input type="date" name="wcb-export-start-date" 185 class="medium-text" value="<?php echo esc_attr( $last_month ); ?>" /> to 186 <input type="date" name="wcb-export-end-date" 187 class="medium-text" value="<?php echo esc_attr( $today ); ?>" /> 188 </td> 189 </tr> 190 <tr> 191 <th>Format</th> 192 <td> 193 <select name="wcb-export-type"> 194 <?php foreach ( get_export_types() as $key => $export_type ) : ?> 195 <option value="<?php echo esc_attr( $key ); ?>"><?php echo esc_html( $export_type['label'] ); ?></option> 196 <?php endforeach; ?> 197 </select> 198 </td> 199 </tr> 200 </table> 201 202 <?php submit_button( 'Download Export' ); ?> 203 </form> 204 <?php 205 } 206 207 /** 208 * Process export requests 209 */ 210 function process_export_request() { 211 if ( empty( $_GET['page'] ) || $_GET['page'] != 'wcb-import-export' ) 212 return; 213 214 if ( empty( $_GET['section'] ) || $_GET['section'] != 'export' ) 215 return; 216 217 if ( empty( $_POST['wcb-request-export'] ) ) 218 return; 219 220 if ( ! current_user_can( 'manage_network' ) || ! check_admin_referer( 'export', 'wcb-request-export' ) ) 221 return; 222 223 $export_types = get_export_types(); 224 225 if ( array_key_exists( $_POST['wcb-export-type'], $export_types ) ) { 226 $export_type = $export_types[ $_POST['wcb-export-type'] ]; 227 } else { 228 $export_type = $export_types['default']; 229 } 230 231 $status = $_POST['wcb-export-status']; 232 if ( ! in_array( $status, array( 'wcb-approved', 'wcb-paid' ) ) ) 233 $status = 'wcb-approved'; 234 235 $start_date = strtotime( $_POST['wcb-export-start-date'] . ' 00:00:00' ); 236 $end_date = strtotime( $_POST['wcb-export-end-date'] . ' 23:59:59' ); 237 $filename = sprintf( $export_type['filename'], date( 'Ymd', $start_date ), date( 'Ymd', $end_date ) ); 238 $filename = sanitize_file_name( $filename ); 239 240 $report = generate_payment_report( array( 241 'status' => $status, 242 'start_date' => $start_date, 243 'end_date' => $end_date, 244 'export_type' => $export_type, 245 ) ); 246 247 if ( is_wp_error( $report ) ) { 248 add_settings_error( 'wcb-dashboard', $report->get_error_code(), $report->get_error_message() ); 249 } else { 250 header( sprintf( 'Content-Type: %s', $export_type['mime_type'] ) ); 251 header( sprintf( 'Content-Disposition: attachment; filename="%s"', $filename ) ); 252 header( 'Cache-control: private' ); 253 header( 'Pragma: private' ); 254 header( 'Expires: Mon, 26 Jul 1997 05:00:00 GMT' ); 255 256 echo $report; 257 die(); 258 } 259 } 260 261 /* 262 * Generate and return the raw payment report contents 263 * 264 * @param array $args 265 * 266 * @return string | WP_Error 267 */ 268 function generate_payment_report( $args ) { 269 global $wpdb; 270 271 $args = wp_parse_args( $args, array( 272 'status' => '', 273 'start_date' => '', 274 'end_date' => '', 275 'export_type' => '', 276 ) ); 277 278 if ( ! is_int( $args['start_date'] ) || ! is_int( $args['end_date'] ) ) { 279 return new WP_Error( 'wcb-bad-dates', 'Invalid start or end date.' ); 280 } 281 282 // todo: support other index tables. 283 $table_name = $wpdb->get_blog_prefix(0) . 'wordcamp_payments_index'; 284 $date_type = 'updated'; 285 286 if ( $args['status'] == 'wcb-paid' ) 287 $date_type = 'paid'; 288 289 $request_indexes = $wpdb->get_results( $wpdb->prepare( " 290 SELECT * 291 FROM `{$table_name}` 292 WHERE `{$date_type}` BETWEEN %d AND %d", 293 $args['start_date'], 294 $args['end_date'] 295 ) ); 296 297 if ( ! is_callable( $args['export_type']['callback'] ) ) 298 return new \WP_Error( 'wcb-invalid-type', 'The export type is invalid.' ); 299 300 $args['request_indexes'] = $request_indexes; 301 302 return call_user_func( $args['export_type']['callback'], $args ); 303 } 304 305 /** 306 * Default CSV report 307 * 308 * @param array $args 309 * 310 * @return string 311 */ 312 function _generate_payment_report_default( $args ) { 313 $args = wp_parse_args( $args, array( 314 'request_indexes' => array(), 315 'status' => '', 316 ) ); 317 318 $column_headings = array( 319 'WordCamp', 'ID', 'Title', 'Status', 'Date Vendor was Paid', 'Creation Date', 'Due Date', 'Amount', 320 'Currency', 'Category', 'Payment Method','Vendor Name', 'Vendor Contact Person', 'Vendor Country', 321 'Check Payable To', 'URL', 'Supporting Documentation Notes', 322 ); 323 324 ob_start(); 325 $report = fopen( 'php://output', 'w' ); 326 327 fputcsv( $report, $column_headings ); 328 329 foreach( $args['request_indexes'] as $index ) { 330 switch_to_blog( $index->blog_id ); 331 332 $request = get_post( $index->post_id ); 333 334 $back_compat_statuses = array( 335 'unpaid' => 'draft', 336 'incomplete' => 'wcb-incomplete', 337 'paid' => 'wcb-paid', 338 ); 339 340 // Map old statuses to new statuses. 341 if ( array_key_exists( $request->post_status, $back_compat_statuses ) ) { 342 $request->post_status = $back_compat_statuses[ $request->post_status ]; 343 } 344 345 if ( $args['status'] && $request->post_status != $args['status'] ) { 346 restore_current_blog(); 347 continue; 348 } 349 350 $currency = get_post_meta( $index->post_id, '_camppayments_currency', true ); 351 $category = get_post_meta( $index->post_id, '_camppayments_payment_category', true ); 352 $date_vendor_paid = get_post_meta( $index->post_id, '_camppayments_date_vendor_paid', true ); 353 354 if ( $date_vendor_paid ) { 355 $date_vendor_paid = date( 'Y-m-d', $date_vendor_paid ); 356 } 357 358 if ( 'null-select-one' === $currency ) { 359 $currency = ''; 360 } 361 362 if ( 'null' === $category ) { 363 $category = ''; 364 } 365 366 $row = array( 367 get_wordcamp_name(), 368 sprintf( '%d-%d', $index->blog_id, $index->post_id ), 369 html_entity_decode( $request->post_title ), 370 $index->status, 371 $date_vendor_paid, 372 date( 'Y-m-d', $index->created ), 373 date( 'Y-m-d', $index->due ), 374 get_post_meta( $index->post_id, '_camppayments_payment_amount', true ), 375 $currency, 376 $category, 377 get_post_meta( $index->post_id, '_camppayments_payment_method', true ), 378 get_post_meta( $index->post_id, '_camppayments_vendor_name', true ), 379 get_post_meta( $index->post_id, '_camppayments_vendor_contact_person', true ), 380 get_post_meta( $index->post_id, '_camppayments_vendor_country', true ), 381 \WCP_Encryption::maybe_decrypt( get_post_meta( $index->post_id, '_camppayments_payable_to', true ) ), 382 get_edit_post_link( $index->post_id ), 383 get_post_meta( $index->post_id, '_camppayments_file_notes', true ), 384 ); 385 386 restore_current_blog(); 387 388 if ( ! empty( $row ) ) { 389 fputcsv( $report, $row ); 390 } 391 } 392 393 fclose( $report ); 394 return ob_get_clean(); 395 } 396 397 /** 398 * Quick Checks via JP Morgan 399 * 400 * @param array $args 401 * 402 * @return string 403 */ 404 function _generate_payment_report_jpm_checks( $args ) { 405 $args = wp_parse_args( $args, array( 406 'request_indexes' => array(), 407 'status' => '', 408 ) ); 409 410 $options = apply_filters( 'wcb_payment_req_check_options', array( 411 'pws_customer_id' => '', 412 'account_number' => '', 413 'contact_email' => '', 414 'contact_phone' => '', 415 ) ); 416 417 $report = fopen( 'php://output', 'w' ); 418 ob_start(); 419 420 // File Header 421 fputcsv( $report, array( 'FILHDR', 'PWS', $options['pws_customer_id'], date( 'm/d/Y' ), date( 'Hi' ) ), ',', '|' ); 422 423 $total = 0; 424 $count = 0; 425 426 if ( false !== get_site_transient( '_wcb_jpm_checks_counter_lock' ) ) { 427 wp_die( 'JPM Checks Export is locked. Please try again later or contact support.' ); 428 } 429 430 // Avoid at least *some* race conditions. 431 set_site_transient( '_wcb_jpm_checks_counter_lock', 1, 30 ); 432 $start = absint( get_site_option( '_wcb_jpm_checks_counter', 0 ) ); 433 434 foreach ( $args['request_indexes'] as $index ) { 435 switch_to_blog( $index->blog_id ); 436 $post = get_post( $index->post_id ); 437 438 if ( $args['status'] && $post->post_status != $args['status'] ) { 439 restore_current_blog(); 440 continue; 441 } 442 443 if ( get_post_meta( $post->ID, '_camppayments_payment_method', true ) != 'Check' ) { 444 restore_current_blog(); 445 continue; 446 } 447 448 $count++; 449 $amount = round( floatval( get_post_meta( $post->ID, '_camppayments_payment_amount', true ) ), 2 ); 450 $total += $amount; 451 452 $payable_to = \WCP_Encryption::maybe_decrypt( get_post_meta( $post->ID, '_camppayments_payable_to', true ) ); 453 $payable_to = html_entity_decode( $payable_to ); // J&J to J&J 454 $countries = \WordCamp_Budgets::get_valid_countries_iso3166(); 455 $vendor_country_code = get_post_meta( $post->ID, '_camppayments_vendor_country_iso3166', true ); 456 if ( ! empty( $countries[ $vendor_country_code ] ) ) { 457 $vendor_country_code = $countries[ $vendor_country_code ]['alpha3']; 458 } 459 460 $description = sanitize_text_field( get_post_meta( $post->ID, '_camppayments_description', true ) ); 461 $description = html_entity_decode( $description ); 462 $invoice_number = get_post_meta( $post->ID, '_camppayments_invoice_number', true ); 463 if ( ! empty( $invoice_number ) ) { 464 $description = sprintf( 'Invoice %s. %s', $invoice_number, $description ); 465 } 466 467 // Payment Header 468 fputcsv( $report, array( 469 'PMTHDR', 470 'USPS', 471 'QKCHECKS', 472 date( 'm/d/Y' ), 473 number_format( $amount, 2, '.', '' ), 474 $options['account_number'], 475 $start + $count, // must be globally unique? 476 $options['contact_email'], 477 $options['contact_phone'], 478 ), ',', '|' ); 479 480 // Payee Name Record 481 fputcsv( $report, array( 482 'PAYENM', 483 substr( $payable_to, 0, 35 ), 484 '', 485 sprintf( '%d-%d', $index->blog_id, $index->post_id ), 486 ), ',', '|' ); 487 488 // Payee Address Record 489 fputcsv( $report, array( 490 'PYEADD', 491 substr( get_post_meta( $post->ID, '_camppayments_vendor_street_address', true ), 0, 35 ), 492 '', 493 ), ',', '|' ); 494 495 // Additional Payee Address Record 496 fputcsv( $report, array( 'ADDPYE', '', '' ), ',', '|' ); 497 498 // Payee Postal Record 499 fputcsv( $report, array( 500 'PYEPOS', 501 substr( get_post_meta( $post->ID, '_camppayments_vendor_city', true ), 0, 35 ), 502 substr( get_post_meta( $post->ID, '_camppayments_vendor_state', true ), 0, 35 ), 503 substr( get_post_meta( $post->ID, '_camppayments_vendor_zip_code', true ), 0, 10 ), 504 substr( $vendor_country_code, 0, 3 ), 505 ), ',', '|' ); 506 507 // Payment Description 508 fputcsv( $report, array( 509 'PYTDES', 510 substr( $description, 0, 122 ), 511 ), ',', '|' ); 512 513 restore_current_blog(); 514 } 515 516 // File Trailer 517 fputcsv( $report, array( 'FILTRL', $count * 6 + 2 ), ',', '|' ); 518 519 // Update counter and unlock 520 $start = absint( get_site_option( '_wcb_jpm_checks_counter', 0 ) ); 521 update_site_option( '_wcb_jpm_checks_counter', $start + $count ); 522 delete_site_transient( '_wcb_jpm_checks_counter_lock' ); 523 524 fclose( $report ); 525 return ob_get_clean(); 526 } 527 528 /** 529 * NACHA via JP Morgan 530 * 531 * @param array $args 532 * 533 * @return string 534 */ 535 function _generate_payment_report_jpm_ach( $args ) { 536 $args = wp_parse_args( $args, array( 537 'request_indexes' => array(), 538 'status' => '', 539 ) ); 540 541 $ach_options = apply_filters( 'wcb_payment_req_ach_options', array( 542 'bank-routing-number' => '', // Immediate Destination (bank routing number) 543 'company-id' => '', // Company ID 544 'financial-inst' => '', // Originating Financial Institution 545 ) ); 546 547 ob_start(); 548 549 // File Header Record 550 551 echo '1'; // Record Type Code 552 echo '01'; // Priority Code 553 echo ' ' . str_pad( substr( $ach_options['bank-routing-number'], 0, 9 ), 9, '0', STR_PAD_LEFT ); 554 echo str_pad( substr( $ach_options['company-id'], 0, 10 ), 10, '0', STR_PAD_LEFT ); // Immediate Origin (TIN) 555 echo date( 'ymd' ); // Transmission Date 556 echo date( 'Hi' ); // Transmission Time 557 echo 'A'; // File ID Modifier 558 echo '094'; // Record Size 559 echo '10'; // Blocking Factor 560 echo '1'; // Format Code 561 echo str_pad( 'JPMORGANCHASE', 23 ); // Destination 562 echo str_pad( 'WCEXPORT', 23 ); // Origin 563 echo str_pad( '', 8 ); // Reference Code (optional) 564 echo PHP_EOL; 565 566 // Batch Header Record 567 568 echo '5'; // Record Type Code 569 echo '200'; // Service Type Code 570 echo 'WordCamp Communi'; // Company Name 571 echo str_pad( '', 20 ); // Blanks 572 echo str_pad( substr( $ach_options['company-id'], 0, 10 ), 10 ); // Company Identification 573 574 // Get the first one in the set. 575 // @todo Split batches by account type. 576 foreach ( $args['request_indexes'] as $index ) { 577 switch_to_blog( $index->blog_id ); 578 $post = get_post( $index->post_id ); 579 $account_type = get_post_meta( $post->ID, '_camppayments_ach_account_type', true ); 580 restore_current_blog(); 581 582 break; 583 } 584 585 $entry_class = $account_type == 'Personal' ? 'PPD' : 'CCD'; 586 echo $entry_class; // Standard Entry Class 587 588 echo 'Vendor Pay'; // Entry Description 589 echo date( 'ymd', _next_business_day_timestamp() ); // Company Description Date 590 echo date( 'ymd', _next_business_day_timestamp() ); // Effective Entry Date 591 echo str_pad( '', 3 ); // Blanks 592 echo '1'; // Originator Status Code 593 echo str_pad( substr( $ach_options['financial-inst'], 0, 8 ), 8 ); // Originating Financial Institution 594 echo '0000001'; // Batch Number 595 echo PHP_EOL; 596 597 $count = 0; 598 $total = 0; 599 $hash = 0; 600 601 foreach ( $args['request_indexes'] as $index ) { 602 switch_to_blog( $index->blog_id ); 603 $post = get_post( $index->post_id ); 604 605 if ( $args['status'] && $post->post_status != $args['status'] ) { 606 restore_current_blog(); 607 continue; 608 } 609 610 if ( get_post_meta( $post->ID, '_camppayments_payment_method', true ) != 'Direct Deposit' ) { 611 restore_current_blog(); 612 continue; 613 } 614 615 $count++; 616 617 // Entry Detail Record 618 619 echo '6'; // Record Type Code 620 621 $transaction_code = $account_type == 'Personal' ? '27' : '22'; 622 echo $transaction_code; // Transaction Code 623 624 // Transit/Routing Number of Destination Bank + Check digit 625 $routing_number = get_post_meta( $post->ID, '_camppayments_ach_routing_number', true ); 626 $routing_number = \WCP_Encryption::maybe_decrypt( $routing_number ); 627 $routing_number = substr( $routing_number, 0, 8 + 1 ); 628 $routing_number = str_pad( $routing_number, 8 + 1 ); 629 $hash += absint( substr( $routing_number, 0, 8 ) ); 630 echo $routing_number; 631 632 // Bank Account Number 633 $account_number = get_post_meta( $post->ID, '_camppayments_ach_account_number', true ); 634 $account_number = \WCP_Encryption::maybe_decrypt( $account_number ); 635 $account_number = substr( $account_number, 0, 17 ); 636 $account_number = str_pad( $account_number, 17 ); 637 echo $account_number; 638 639 // Amount 640 $amount = round( floatval( get_post_meta( $post->ID, '_camppayments_payment_amount', true ) ), 2 ); 641 $total += $amount; 642 $amount = str_pad( number_format( $amount, 2, '', '' ), 10, '0', STR_PAD_LEFT ); 643 echo $amount; 644 645 // Individual Identification Number 646 echo str_pad( sprintf( '%d-%d', $index->blog_id, $index->post_id ), 15 ); 647 648 // Individual Name 649 $name = get_post_meta( $post->ID, '_camppayments_ach_account_holder_name', true ); 650 $name = \WCP_Encryption::maybe_decrypt( $name ); 651 $name = substr( $name, 0, 22 ); 652 $name = str_pad( $name, 22 ); 653 echo $name; 654 655 echo ' '; // User Defined Data 656 echo '0'; // Addenda Record Indicator 657 658 // Trace Number 659 echo str_pad( substr( $ach_options['bank-routing-number'], 0, 8 ), 8, '0', STR_PAD_LEFT ); // routing number 660 echo str_pad( $count, 7, '0', STR_PAD_LEFT ); // sequence number 661 echo PHP_EOL; 662 } 663 664 // Batch Trailer Record 665 666 echo '8'; // Record Type Code 667 echo '200'; // Service Class Code 668 echo str_pad( $count, 6, '0', STR_PAD_LEFT ); // Entry/Addenda Count 669 echo str_pad( substr( $hash, -10 ), 10, '0', STR_PAD_LEFT ); // Entry Hash 670 echo str_pad( number_format( $total, 2, '', '' ), 12, '0', STR_PAD_LEFT ); // Total Debit Entry Dollar Amount 671 echo str_pad( 0, 12, '0', STR_PAD_LEFT ); // Total Credit Entry Dollar Amount 672 echo str_pad( substr( $ach_options['company-id'], 0, 10 ), 10 ); // Company ID 673 echo str_pad( '', 25 ); // Blanks 674 echo str_pad( substr( $ach_options['financial-inst'], 0, 8 ), 8 ); // Originating Financial Institution 675 echo '0000001'; // Batch Number 676 echo PHP_EOL; 677 678 679 // File Trailer Record 680 681 echo '9'; // Record Type Code 682 echo '000001'; // Batch Count 683 echo str_pad( ceil( $count / 10 ), 6, '0', STR_PAD_LEFT ); // Block Count 684 echo str_pad( $count, 8, '0', STR_PAD_LEFT ); // Entry/Addenda Count 685 echo str_pad( substr( $hash, -10 ), 10, '0', STR_PAD_LEFT ); // Entry Hash 686 echo str_pad( number_format( $total, 2, '', '' ), 12, '0', STR_PAD_LEFT ); // Total Debit Entry Dollar Amount 687 echo str_pad( 0, 12, '0', STR_PAD_LEFT ); // Total Credit Entry Dollar Amount 688 echo str_pad( '', 39 ); // Blanks 689 echo PHP_EOL; 690 691 // The file must have a number of lines that is a multiple of 10 (e.g. 10, 20, 30). 692 echo str_repeat( PHP_EOL, 10 - ( ( 4 + $count ) % 10 ) - 1 ); 693 return ob_get_clean(); 694 } 695 696 /** 697 * Wires via JP Morgan 698 * 699 * @param array $args 700 * 701 * @return string 702 */ 703 function _generate_payment_report_jpm_wires( $args ) { 704 $args = wp_parse_args( $args, array( 705 'request_indexes' => array(), 706 'status' => '', 707 ) ); 708 709 ob_start(); 710 $report = fopen( 'php://output', 'w' ); 711 712 // JPM Header 713 fputcsv( $report, array( 'HEADER', gmdate( 'YmdHis' ), '1' ) ); 714 715 $total = 0; 716 $count = 0; 717 718 foreach ( $args['request_indexes'] as $index ) { 719 switch_to_blog( $index->blog_id ); 720 $post = get_post( $index->post_id ); 721 722 if ( $args['status'] && $post->post_status != $args['status'] ) { 723 restore_current_blog(); 724 continue; 725 } 726 727 // Only wires here. 728 if ( get_post_meta( $post->ID, '_camppayments_payment_method', true ) != 'Wire' ) { 729 restore_current_blog(); 730 continue; 731 } 732 733 $amount = round( floatval( get_post_meta( $post->ID, '_camppayments_payment_amount', true ) ), 2); 734 $total += $amount; 735 $count += 1; 736 737 // If account starts with two letters, it's most likely an IBAN 738 $account = get_post_meta( $post->ID, '_camppayments_beneficiary_account_number', true ); 739 $account = \WCP_Encryption::maybe_decrypt( $account ); 740 $account = preg_replace( '#\s#','', $account ); 741 $account_type = preg_match( '#^[a-z]{2}#i', $account ) ? 'IBAN' : 'ACCT'; 742 743 $row = array( 744 '1-input-type' => 'P', 745 '2-payment-method' => 'WIRES', 746 '3-debit-bank-id' => apply_filters( 'wcb_payment_req_bank_id', '' ), // external file 747 '4-account-number' => apply_filters( 'wcb_payment_req_bank_number', '' ), // external file 748 '5-bank-to-bank' => 'N', 749 '6-txn-currency' => get_post_meta( $post->ID, '_camppayments_currency', true ), 750 '7-txn-amount' => number_format( $amount, 2, '.', '' ), 751 '8-equiv-amount' => '', 752 '9-clearing' => '', 753 '10-ben-residence' => '', 754 '11-rate-type' => '', 755 '12-blank' => '', 756 '13-value-date' => '', 757 758 '14-id-type' => $account_type, 759 '15-id-value' => $account, 760 '16-ben-name' => substr( \WCP_Encryption::maybe_decrypt( 761 get_post_meta( $post->ID, '_camppayments_beneficiary_name', true ) ), 0, 35 ), 762 '17-address-1' => substr( \WCP_Encryption::maybe_decrypt( 763 get_post_meta( $post->ID, '_camppayments_beneficiary_street_address', true ) ), 0, 35 ), 764 '18-address-2' => '', 765 '19-city-state-zip' => substr( sprintf( '%s %s %s', 766 \WCP_Encryption::maybe_decrypt( get_post_meta( $post->ID, '_camppayments_beneficiary_city', true ) ), 767 \WCP_Encryption::maybe_decrypt( get_post_meta( $post->ID, '_camppayments_beneficiary_state', true ) ), 768 \WCP_Encryption::maybe_decrypt( get_post_meta( $post->ID, '_camppayments_beneficiary_zip_code', true ) ) 769 ), 0, 32 ), 770 '20-blank' => '', 771 '21-country' => \WCP_Encryption::maybe_decrypt( 772 get_post_meta( $post->ID, '_camppayments_beneficiary_country_iso3166', true ) ), 773 '22-blank' => '', 774 '23-blank' => '', 775 776 '24-id-type' => 'SWIFT', 777 '25-id-value' => get_post_meta( $post->ID, '_camppayments_bank_bic', true ), 778 '26-ben-bank-name' => substr( get_post_meta( $post->ID, '_camppayments_bank_name', true ), 0, 35 ), 779 '27-ben-bank-address-1' => substr( get_post_meta( $post->ID, '_camppayments_bank_street_address', true ), 0, 35 ), 780 '28-ben-bank-address-2' => '', 781 '29-ben-bank-address-3' => substr( sprintf( '%s %s %s', 782 get_post_meta( $post->ID, '_camppayments_bank_city', true ), 783 get_post_meta( $post->ID, '_camppayments_bank_state', true ), 784 get_post_meta( $post->ID, '_camppayments_bank_zip_code', true ) 785 ), 0, 35 ), 786 '30-ben-bank-country' => get_post_meta( $post->ID, '_camppayments_bank_country_iso3166', true ), 787 '31-supl-id-type' => '', 788 '32-supl-id-value' => '', 789 790 '33-blank' => '', 791 '34-blank' => '', 792 '35-blank' => '', 793 '36-blank' => '', 794 '37-blank' => '', 795 '38-blank' => '', 796 '39-blank' => '', 797 798 // Filled out later if not empty. 799 '40-id-type' => '', 800 '41-id-value' => '', 801 '42-interm-bank-name' => '', 802 '43-interm-bank-address-1' => '', 803 '44-interm-bank-address-2' => '', 804 '45-interm-bank-address-3' => '', 805 '46-interm-bank-country' => '', 806 '47-supl-id-type' => '', 807 '48-supl-id-value' => '', 808 809 '49-id-type' => '', 810 '50-id-value' => '', 811 '51-party-name' => '', 812 '52-party-address-1' => '', 813 '53-party-address-2' => '', 814 '54-party-address-3' => '', 815 '55-party-country' => '', 816 817 '56-blank' => '', 818 '57-blank' => '', 819 '58-blank' => '', 820 '59-blank' => '', 821 '60-blank' => '', 822 '61-blank' => '', 823 '62-blank' => '', 824 '63-blank' => '', 825 '64-blank' => '', 826 '65-blank' => '', 827 '66-blank' => '', 828 '67-blank' => '', 829 '68-blank' => '', 830 '69-blank' => '', 831 '70-blank' => '', 832 '71-blank' => '', 833 '72-blank' => '', 834 '73-blank' => '', 835 836 '74-ref-text' => substr( get_post_meta( $post->ID, '_camppayments_invoice_number', true ), 0, 16 ), 837 '75-internal-ref' => '', 838 '76-on-behalf-of' => '', 839 840 '77-detial-1' => '', 841 '78-detial-2' => '', 842 '79-detial-3' => '', 843 '80-detail-4' => '', 844 845 '81-blank' => '', 846 '82-blank' => '', 847 '83-blank' => '', 848 '84-blank' => '', 849 '85-blank' => '', 850 '86-blank' => '', 851 '87-blank' => '', 852 '88-blank' => '', 853 854 '89-reporting-code' => '', 855 '90-country' => '', 856 '91-inst-1' => '', 857 '92-inst-2' => '', 858 '93-inst-3' => '', 859 '94-inst-code-1' => '', 860 '95-inst-text-1' => '', 861 '96-inst-code-2' => '', 862 '97-inst-text-2' => '', 863 '98-inst-code-3' => '', 864 '99-inst-text-3' => '', 865 866 '100-stor-code-1' => '', 867 '101-stor-line-2' => '', // Hmm? 868 '102-stor-code-2' => '', 869 '103-stor-line-2' => '', 870 '104-stor-code-3' => '', 871 '105-stor-line-3' => '', 872 '106-stor-code-4' => '', 873 '107-stor-line-4' => '', 874 '108-stor-code-5' => '', 875 '109-stor-line-5' => '', 876 '110-stor-code-6' => '', 877 '111-stor-line-6' => '', 878 879 '112-priority' => '', 880 '113-blank' => '', 881 '114-charges' => '', 882 '115-blank' => '', 883 '116-details' => '', 884 '117-note' => substr( sprintf( 'wcb-%d-%d', $index->blog_id, $index->post_id ), 0, 70 ), 885 ); 886 887 // If an intermediary bank is given. 888 $interm_swift = get_post_meta( $post->ID, '_camppayments_interm_bank_swift', true ); 889 if ( ! empty( $iterm_swift ) ) { 890 $row['40-id-type'] = 'SWIFT'; 891 $row['41-id-value'] = $interm_swift; 892 893 $row['42-interm-bank-name'] = substr( get_post_meta( $post->ID, '_camppayments_interm_bank_name', true ), 0, 35 ); 894 $row['43-interm-bank-address-1'] = substr( get_post_meta( $post->ID, '_camppayments_interm_bank_street_address', true ), 0, 35 ); 895 896 $row['44-interm-bank-address-2'] = ''; 897 $row['45-interm-bank-address-3'] = substr( sprintf( '%s %s %s', 898 get_post_meta( $post->ID, '_camppayments_interm_bank_city', true ), 899 get_post_meta( $post->ID, '_camppayments_interm_bank_state', true ), 900 get_post_meta( $post->ID, '_camppayments_interm_bank_zip_code', true ) 901 ), 0, 32 ); 902 903 $row['46-interm-bank-country'] = get_post_meta( $post->ID, '_camppayments_interm_bank_country_iso3166', true ); 904 905 $row['47-supl-id-type'] = 'ACCT'; 906 $row['48-supl-id-value'] = get_post_meta( $post->ID, '_camppayments_interm_bank_account', true ); 907 } 908 909 // Use for debugging. 910 // print_r( $row ); 911 912 fputcsv( $report, array_values( $row ) ); 913 restore_current_blog(); 914 } 915 916 // JPM Trailer 917 fputcsv( $report, array( 'TRAILER', $count, $total ) ); 918 919 fclose( $report ); 920 $results = ob_get_clean(); 921 922 // JPM chokes on accents and non-latin characters. 923 $results = remove_accents( $results ); 924 return $results; 925 } 926 927 /** 928 * Exclude weekends and JPM holidays. 929 * 930 * Needs to be updated every year. 931 * 932 * @return int Timestamp. 933 */ 934 function _next_business_day_timestamp() { 935 static $timestamp; 936 937 if ( isset( $timestamp ) ) 938 return $timestamp; 939 940 $holidays = array( 941 date( 'Ymd', strtotime( 'Friday, January 1, 2016' ) ), 942 date( 'Ymd', strtotime( 'Monday, January 18, 2016' ) ), 943 date( 'Ymd', strtotime( 'Monday, February 15, 2016' ) ), 944 date( 'Ymd', strtotime( 'Monday, May 30, 2016' ) ), 945 date( 'Ymd', strtotime( 'Monday, July 4, 2016' ) ), 946 date( 'Ymd', strtotime( 'Monday, September 5, 2016' ) ), 947 date( 'Ymd', strtotime( 'Friday, November 11, 2016' ) ), 948 date( 'Ymd', strtotime( 'Thursday, November 24, 2016' ) ), 949 date( 'Ymd', strtotime( 'Monday, December 26, 2016' ) ), 950 ); 951 952 $timestamp = strtotime( 'today + 1 weekday' ); 953 $attempts = 5; 954 955 while ( in_array( date( 'Ymd', $timestamp ), $holidays ) ) { 956 $timestamp = strtotime( '+ 1 weekday', $timestamp ); 957 $attempts--; 958 959 if ( ! $attempts ) 960 break; 961 } 962 963 return $timestamp; 964 } 965 966 /** 34 967 * Remove the empty Budgets submenu item 35 968 * … … 37 970 */ 38 971 function remove_budgets_submenu() { 39 remove_submenu_page( 'wordcamp-budgets-dashboard', 'wordcamp-budgets-dashboard' );972 remove_submenu_page( 'wordcamp-budgets-dashboard', 'wordcamp-budgets-dashboard' ); 40 973 } 41 974 … … 43 976 * Enqueue scripts and styles 44 977 */ 45 function enqueue_scripts( ) {978 function enqueue_scripts( $hook ) { 46 979 wp_enqueue_style( 47 980 'wordcamp-budgets-dashboard', … … 50 983 1 51 984 ); 985 986 if ( $hook == 'budgets_page_wcb-import-export' ) { 987 wp_enqueue_script( 'jquery-ui-datepicker' ); 988 wp_enqueue_style( 'jquery-ui' ); 989 wp_enqueue_style( 'wp-datepicker-skins' ); 990 } 52 991 } 53 992 … … 62 1001 function format_amount( $amount, $currency ) { 63 1002 $formatted_amount = ''; 64 $amount = \WordCamp_Budgets::validate_amount( $amount );1003 $amount = \WordCamp_Budgets::validate_amount( $amount ); 65 1004 66 1005 if ( false === strpos( $currency, 'null' ) && $amount ) { … … 94 1033 global $wpdb; 95 1034 96 $from = strtolower( $from );97 $to = strtolower( $to );1035 $from = strtolower( $from ); 1036 $to = strtolower( $to ); 98 1037 $cache_key = md5( sprintf( 'wcp-exchange-rate-%s:%s', $from, $to ) ); 99 1038 … … 107 1046 108 1047 $request = wp_remote_get( esc_url_raw( $url ) ); 109 $body = json_decode( wp_remote_retrieve_body( $request ), true );1048 $body = json_decode( wp_remote_retrieve_body( $request ), true ); 110 1049 111 1050 if ( ! empty( $body['query']['results']['rate']['Ask'] ) ) {
Note: See TracChangeset
for help on using the changeset viewer.