<!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>[2260] sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-payments-network: WordCamp Budgets Dashboard: Reorganize to make room for additional modules.</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/2260">2260</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/2260","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>iandunn</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2016-01-08 20:42:19 +0000 (Fri, 08 Jan 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 Dashboard: Reorganize to make room for additional modules.

Soon we'll be adding modules for Budgets and Sponsor Invoices, in addition to the existing one for Payment Requests.</pre>

<h3>Added Paths</h3>
<ul>
<li><a href="#sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcamppaymentsnetworkbootstrapphp">sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-payments-network/bootstrap.php</a></li>
<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_htmlwpcontentpluginswordcamppaymentsnetworkincludespaymentrequestslisttablephp">sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-payments-network/includes/payment-requests-list-table.php</a></li>
</ul>

<h3>Removed Paths</h3>
<ul>
<li><a href="#sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcamppaymentsnetworkincludesclasslisttablephp">sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-payments-network/includes/class-list-table.php</a></li>
<li><a href="#sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcamppaymentsnetworkwordcamppaymentsnetworkphp">sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-payments-network/wordcamp-payments-network.php</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcamppaymentsnetworkbootstrapphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-payments-network/bootstrap.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/bootstrap.php                           (rev 0)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-payments-network/bootstrap.php     2016-01-08 20:42:19 UTC (rev 2260)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,23 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+
+/*
+* Plugin Name: WordCamp Budgets Dashboard
+* Description: Provides an overview of WordCamp budgets, payment requests, and sponsor invoices across the network.
+* Version:     0.1
+* Author:      WordCamp.org
+* Author URI:  http://wordcamp.org
+* License:     GPLv2 or later
+* Network:     true
+*/
+
+namespace WordCamp\Budgets_Dashboard;
+
+defined( 'WPINC' ) or die();
+
+if ( is_admin() ) {
+       require_once( __DIR__ . '/includes/payment-requests-dashboard.php' );
+
+       $GLOBALS['Payment_Requests_Dashboard'] = new \Payment_Requests_Dashboard();
+
+       add_action( 'plugins_loaded', array( $GLOBALS['Payment_Requests_Dashboard'], 'plugins_loaded' ) );
+}
</ins></span></pre></div>
<a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcamppaymentsnetworkincludesclasslisttablephp"></a>
<div class="delfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Deleted: sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-payments-network/includes/class-list-table.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/class-list-table.php   2016-01-08 20:11:47 UTC (rev 2259)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-payments-network/includes/class-list-table.php     2016-01-08 20:42:19 UTC (rev 2260)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1,223 +0,0 @@
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-<?php
-/**
- * This list table class handles the output of data
- * in the Payment Network Dashboard. Use the set_view() method
- * to set a specific view/filter for the data.
- *
- * Note: Uses switch_to_blog() excessively.
- */
-class WordCamp_Payments_Network_List_Table extends WP_List_Table {
-
-       /**
-        * Used by the parent class, returns an array of
-        * columns to display.
-        */
-       public function get_columns() {
-               return array(
-                       'payment' => 'Payment',
-                       'status' => 'Status',
-                       'category' => 'Category',
-                       'due' => 'Due',
-                       'amount' => 'Amount',
-                       'method' => 'Method',
-                       'attachments' => 'Attachments',
-               );
-       }
-
-       /**
-        * Tells which columns are sortable. The array values
-        * are the ones passed on to the orderby request argument.
-        */
-       public function get_sortable_columns() {
-               return array(
-                       'category' => 'category',
-                       'due' => 'due',
-                       'status' => 'status',
-                       'method' => 'method',
-               );
-       }
-
-       /**
-        * Outputs inline CSS to be used with the list table.
-        */
-       public function print_inline_css() {
-               ?>
-               <style>
-               #wcp-list-table .search-box {
-                       margin-top: 12px;
-               }
-
-               #wcp-list-table .manage-column.column-payment {
-                       width: 30%;
-               }
-
-               #wcp-list-table .manage-column {
-                       width: 10%;
-               }
-               </style>
-               <?php
-       }
-
-       /**
-        * Parses query arguments and queries the index table in the database.
-        */
-       public function prepare_items() {
-               global $wpdb;
-
-               $sql = sprintf( "SELECT SQL_CALC_FOUND_ROWS blog_id, post_id FROM `%s` WHERE 1=1 ", WordCamp_Payments_Network_Tools::get_table_name() );
-               $view = WordCamp_Payments_Network_Tools::get_current_tab();
-               $where = '';
-               $orderby = '';
-               $limit = '';
-
-               $per_page = 10;
-               $orderby = 'due';
-               $order = 'asc';
-
-               if ( 'overdue' == $view ) {
-                       $where .= $wpdb->prepare( " AND `status` = 'unpaid' AND `due` > 0 AND `due` <= %d ", time() );
-               } elseif ( 'pending' == $view ) {
-                       $where .= " AND `status` = 'unpaid' ";
-               } elseif ( 'paid' == $view ) {
-                       $where .= " AND `status` = 'paid' ";
-                       $orderby = 'created';
-                       $order = 'desc';
-               } elseif( 'incomplete' == $view ) {
-                       $where .= " AND `status` = 'incomplete' ";
-               }
-
-               if ( ! empty( $_REQUEST['s'] ) ) {
-                       $where .= $wpdb->prepare( " AND `keywords` LIKE %s ", '%' . $wpdb->esc_like( wp_unslash( $_REQUEST['s'] ) ) . '%' );
-               }
-
-               if ( ! empty( $_REQUEST['orderby'] ) && in_array( $_REQUEST['orderby'], array_values( $this->get_sortable_columns() ) ) )
-                       $orderby = $_REQUEST['orderby'];
-
-               if ( ! empty( $_REQUEST['order'] ) && in_array( $_REQUEST['order'], array( 'asc', 'desc' ) ) )
-                       $order = $_REQUEST['order'];
-
-               $orderby = sprintf( " ORDER BY `%s` %s ", $orderby, $order );
-
-               $paged = isset( $_REQUEST['paged'] ) ? absint( $_REQUEST['paged'] ) : 1;
-
-               $limit .= sprintf( " LIMIT %d OFFSET %d ", $per_page, $per_page * ( $paged - 1 ) );
-
-               $sql .= $where . $orderby . $limit;
-
-               $this->items = $wpdb->get_results( $sql );
-
-               $total_items = $wpdb->get_var( "SELECT FOUND_ROWS();" );
-               $this->set_pagination_args( array(
-                       'total_items' => $total_items,
-                       'total_pages' => ceil( $total_items / $per_page ),
-                       'per_page' => $per_page,
-               ) );
-               return;
-       }
-
-       /**
-        * Output a single row in the list table.
-        *
-        * Holy cow, switch to blog! Please note that all the
-        * column_* methods are being run in a switched context.
-        */
-       public function single_row( $item ) {
-               switch_to_blog( $item->blog_id );
-               $request = get_post( $item->post_id );
-               parent::single_row( $request );
-               restore_current_blog();
-       }
-
-       /**
-        * Return the payment column contents.
-        *
-        * Note: runs in a switch_to_blog() context.
-        */
-       public function column_payment( $request ) {
-               $edit_post_link = add_query_arg( array( 'post' => $request->ID, 'action' => 'edit' ), admin_url( 'post.php' ) );
-               $actions = array(
-                       'view-all' => sprintf( '<a href="%s" target="_blank">View All</a>', esc_url( admin_url( 'edit.php?post_type=wcp_payment_request' ) ) ),
-               );
-
-               return sprintf( '<a href="%s" class="row-title" target="_blank">%s</a>%s',
-                       esc_url( $edit_post_link ),
-                       esc_html( $request->post_title ),
-                       $this->row_actions( $actions )
-               );
-       }
-
-       /**
-        * Note: runs in a switch_to_blog() context.
-        */
-       public function column_status( $request ) {
-               return esc_html( ucwords( $request->post_status ) );
-       }
-
-       /**
-        * Note: runs in a switch_to_blog() context.
-        */
-       public function column_category( $request ) {
-               require_once( WP_PLUGIN_DIR . '/wordcamp-payments/classes/payment-request.php' );
-               $categories        = WCP_Payment_Request::get_payment_categories();
-               $selected_category = get_post_meta( $request->ID, '_camppayments_payment_category', true );
-
-               return isset( $categories[ $selected_category ] ) ? $categories[ $selected_category ] : '';
-       }
-
-       /**
-        * Note: runs in a switch_to_blog() context.
-        */
-       public function column_amount( $request ) {
-               $currency = get_post_meta( $request->ID, '_camppayments_currency', true );
-               $amount = get_post_meta( $request->ID, '_camppayments_payment_amount', true );
-
-               $amount = preg_replace( '#[^\d.-]+#', '', $amount );
-               $amount = floatval( $amount );
-
-               if ( strpos( $currency, 'null' ) === false && $amount ) {
-                       $output = sprintf( '%s&nbsp;%s', esc_html( number_format( $amount, 2 ) ), esc_html( $currency ) );
-
-                       if ( $currency != 'USD' ) {
-                               $usd_amount = WordCamp_Payments_Network_Tools::convert_currency( $currency, 'usd', $amount );
-                               if ( $usd_amount )
-                                       $output .= sprintf( '<br />~&nbsp;%s&nbsp;USD', esc_html( number_format( $usd_amount, 2 ) ) );
-                       }
-
-                       return $output;
-               } elseif ( $amount ) {
-                       return esc_html( $amount );
-               }
-       }
-
-       /**
-        * Note: runs in a switch_to_blog() context.
-        */
-       public function column_due( $request ) {
-               $due = get_post_meta( $request->ID, '_camppayments_due_by', true );
-               return $due ? date( 'Y-m-d', $due ) : '';
-       }
-
-       /**
-        * Note: runs in a switch_to_blog() context.
-        */
-       public function column_method( $request ) {
-               $method = get_post_meta( $request->ID, '_camppayments_payment_method', true );
-               return esc_html( $method );
-       }
-
-       /**
-        * Note: runs in a switch_to_blog() context.
-        */
-       public function column_attachments( $request ) {
-               $attachments = get_children( array( 'post_parent' => $request->ID ) );
-               $attachments = array_map( 'wp_get_attachment_url', wp_list_pluck( $attachments, 'ID' ) );
-
-               $output = array();
-               foreach ( $attachments as $attachment ) {
-                       $output[] = sprintf( '<a href="%s" target="_blank" class="dashicons dashicons-media-default" title="%s"></a>',
-                               esc_url( $attachment ), esc_attr( $attachment ) );
-               }
-
-               return implode( '', $output );
-       }
-}
</del><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of file
</span></span></pre></div>
<a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcamppaymentsnetworkincludespaymentrequestsdashboardphpfromrev2255sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcamppaymentsnetworkwordcamppaymentsnetworkphp"></a>
<div class="copfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Copied: sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-payments-network/includes/payment-requests-dashboard.php (from rev 2255, sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-payments-network/wordcamp-payments-network.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                         (rev 0)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-payments-network/includes/payment-requests-dashboard.php   2016-01-08 20:42:19 UTC (rev 2260)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,530 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+
+class Payment_Requests_Dashboard {
+       public static $list_table;
+       public static $db_version = 6;
+
+       /**
+        * Runs during plugins_loaded, doh.
+        */
+       public static function plugins_loaded() {
+               $current_site = get_current_site();
+
+               // Schedule the aggregate event only on the main blog in the network.
+               if ( get_current_blog_id() == $current_site->blog_id && ! wp_next_scheduled( 'wordcamp_payments_aggregate' ) )
+                       wp_schedule_event( time(), 'hourly', 'wordcamp_payments_aggregate' );
+
+               add_action( 'wordcamp_payments_aggregate', array( __CLASS__, 'aggregate' ) );
+               add_action( 'admin_enqueue_scripts',  array( __CLASS__, 'enqueue_assets' ) );
+               add_action( 'network_admin_menu', array( __CLASS__, 'network_admin_menu' ) );
+               add_action( 'init', array( __CLASS__, 'upgrade' ) );
+               add_action( 'init', array( __CLASS__, 'process_export_request' ) );
+
+               // Diff-based updates to the index.
+               add_action( 'save_post', array( __CLASS__, 'save_post' ) );
+               add_action( 'delete_post', array( __CLASS__, 'delete_post' ) );
+
+               if ( ! empty( $_GET['wcp-debug-network'] ) && current_user_can( 'manage_network' ) )
+                       add_action( 'admin_init', function() { do_action( 'wordcamp_payments_aggregate' ); }, 99 );
+       }
+
+       /**
+        * Returns the name of the custom table.
+        */
+       public static function get_table_name() {
+               global $wpdb;
+               return $wpdb->get_blog_prefix(0) . 'wordcamp_payments_index';
+       }
+
+       /**
+        * Upgrade routine, makes sure that our schema is up to date.
+        */
+       public static function upgrade() {
+               global $wpdb;
+
+               // Don't attempt to perform upgrades outside of the dashboard.
+               if ( ! is_admin() )
+                       return;
+
+               $current_version = get_site_option( 'wcp_network_db_version', 0 );
+               if ( version_compare( $current_version, self::$db_version, '>=' ) )
+                       return;
+
+               require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
+
+               $charset_collate = "DEFAULT CHARACTER SET {$wpdb->charset} COLLATE {$wpdb->collate}";
+               $sql = sprintf( "CREATE TABLE %s (
+                       id int(11) unsigned NOT NULL auto_increment,
+                       blog_id int(11) unsigned NOT NULL default '0',
+                       post_id int(11) unsigned NOT NULL default '0',
+                       created int(11) unsigned NOT NULL default '0',
+                       paid int(11) unsigned NOT NULL default '0',
+                       category varchar(255) NOT NULL default '',
+                       method varchar(255) NOT NULL default '',
+                       due int(11) unsigned NOT NULL default '0',
+                       status varchar(255) NOT NULL default '',
+                       keywords text NOT NULL default '',
+                       PRIMARY KEY  (id),
+                       KEY blog_post_id (blog_id, post_id),
+                       KEY due (due),
+                       KEY status (status)
+               ) %s;", self::get_table_name(), $charset_collate );
+
+               dbDelta( $sql );
+
+               update_site_option( 'wcp_network_db_version', self::$db_version );
+       }
+
+       /**
+        * Runs on a cron job, reads data from all sites in the network
+        * and builds an index table for future queries.
+        */
+       public static function aggregate() {
+               global $wpdb;
+
+               // Register the custom payment statuses so that we can filter posts to include only them, in order to exclude trashed posts
+               require_once( WP_PLUGIN_DIR . '/wordcamp-payments/classes/payment-request.php' );
+               WCP_Payment_Request::register_post_statuses();
+
+               // Truncate existing table.
+               $wpdb->query( sprintf( "TRUNCATE TABLE %s;", self::get_table_name() ) );
+
+               $blogs = $wpdb->get_col( $wpdb->prepare( "SELECT blog_id FROM `{$wpdb->blogs}` WHERE site_id = %d ORDER BY last_updated DESC LIMIT %d;", $wpdb->siteid, 1000 ) );
+               foreach ( $blogs as $blog_id ) {
+                       switch_to_blog( $blog_id );
+
+                       $paged = 1;
+                       while ( $requests = get_posts( array(
+                               'paged' => $paged++,
+                               'post_status' => array( 'paid', 'unpaid', 'incomplete' ),
+                               'post_type' => 'wcp_payment_request',
+                               'posts_per_page' => 20,
+                       ) ) ) {
+                               foreach ( $requests as $request ) {
+                                       $wpdb->insert( self::get_table_name(), self::prepare_for_index( $request ) );
+                               }
+                       }
+
+                       restore_current_blog();
+               }
+       }
+
+       /**
+        * Given a $request (could be a post_id) create an array that can
+        * be used with $wpdb->update() or $wpdb->insert() to add or update
+        * an index entry.
+        */
+       public static function prepare_for_index( $request ) {
+               $request = get_post( $request );
+               $categories = WCP_Payment_Request::get_payment_categories();
+
+               // All things search.
+               $keywords = array( $request->post_title );
+
+               $category_slug = get_post_meta( $request->ID, '_camppayments_payment_category', true );
+               if ( ! empty( $categories[ $category_slug ] ) )
+                       $keywords[] = $categories[ $category_slug ];
+
+               $payment_method = get_post_meta( $request->ID, '_camppayments_payment_method', true );
+               if ( ! empty( $payment_method ) )
+                       $keywords[] = $payment_method;
+
+               return array(
+                       'blog_id' => get_current_blog_id(),
+                       'post_id' => $request->ID,
+                       'created' => get_post_time( 'U', true, $request->ID ),
+                               // todo Sometimes this is empty. Core normally catches this (r8636), but misses in our case because we don't use drafts. #1350-meta might have the side-effect of solving this.
+                       'paid'    => absint( get_post_meta( $request->ID, '_camppayments_date_vendor_paid', true ) ),
+                       'due' => absint( get_post_meta( $request->ID, '_camppayments_due_by', true ) ),
+                       'status' => $request->post_status,
+                       'method' => $payment_method,
+                       'category' => $category_slug,
+                       'keywords' => json_encode( $keywords ),
+               );
+       }
+
+       /**
+        * Runs during save_post, make sure our index is up to date.
+        */
+       public static function save_post( $post_id ) {
+               global $wpdb;
+
+               $request = get_post( $post_id );
+               if ( 'wcp_payment_request' != $request->post_type )
+                       return;
+
+               $table_name = self::get_table_name();
+               $entry_id = $wpdb->get_var( $wpdb->prepare( "SELECT id FROM {$table_name} WHERE `blog_id` = %d AND `post_id` = %d LIMIT 1;", get_current_blog_id(), $request->ID ) );
+
+               // Insert or update this record.
+               if ( empty( $entry_id ) ) {
+                       $wpdb->insert( $table_name, self::prepare_for_index( $request ) );
+               } else {
+                       $wpdb->update( $table_name, self::prepare_for_index( $request ), array( 'id' => $entry_id ) );
+               }
+       }
+
+       /**
+        * Delete an index query when a request post has been deleted.
+        */
+       public static function delete_post( $post_id ) {
+               global $wpdb;
+
+               $request = get_post( $post_id );
+               if ( 'wcp_payment_request' != $request->post_type )
+                       return;
+
+               $table_name = self::get_table_name();
+               $wpdb->query( $wpdb->prepare( "DELETE FROM {$table_name} WHERE `blog_id` = %d AND `post_id` = %d LIMIT 1;", get_current_blog_id(), $request->ID ) );
+       }
+
+       /**
+        * Create a network admin menu item entry.
+        */
+       public static function network_admin_menu() {
+               $dashboard = add_dashboard_page( 'WordCamp Payments Requests', 'Payments Requests', 'manage_network', 'wcp-dashboard', array( __CLASS__, 'render_dashboard' ) );
+               add_action( 'load-' . $dashboard, array( __CLASS__, 'pre_render_dashboard' ) );
+       }
+
+       /**
+        * 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' );
+               }
+       }
+
+       /**
+        * Renders the Dashboard - Payments screen.
+        */
+       public static function render_dashboard() {
+               ?>
+
+               <div class="wrap">
+                       <h1>Payment Requests</h1>
+
+                       <?php settings_errors(); ?>
+
+                       <h3 class="nav-tab-wrapper"><?php self::render_dashboard_tabs(); ?></h3>
+
+                       <?php
+                               if ( 'export' == self::get_current_tab() ) {
+                                       self::render_export_tab();
+                               } else {
+                                       self::render_table_tabs();
+                               }
+                       ?>
+
+               </div> <!-- /wrap -->
+
+               <?php
+       }
+
+       /**
+        * Render the table tabs, like Overview, Pending, etc
+        */
+       protected static function render_table_tabs() {
+               ?>
+
+               <?php self::$list_table->print_inline_css(); ?>
+
+               <div id="wcp-list-table">
+                       <?php self::$list_table->prepare_items(); ?>
+
+                       <form id="posts-filter" action="" method="get">
+                               <input type="hidden" name="page" value="wcp-dashboard" />
+                               <input type="hidden" name="wcp-section" value="<?php echo esc_attr( self::get_current_tab() ); ?>" />
+                               <?php self::$list_table->search_box( __( 'Search Payments', 'wordcamporg' ), 'wcp' ); ?>
+                               <?php self::$list_table->display(); ?>
+                       </form>
+               </div>
+
+               <?php
+       }
+
+       /**
+        * 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;
+               }
+
+               $start_date = strtotime( $_POST['wcpn_export_start_date'] . ' 00:00:00' );
+               $end_date   = strtotime( $_POST['wcpn_export_end_date']   . ' 23:59:59' );
+               $filename   = sanitize_file_name( sprintf( 'wordcamp-payments-%s-to-%s.csv', date( 'Y-m-d', $start_date ), date( 'Y-m-d', $end_date ) ) );
+
+               $report = self::generate_payment_report( $_POST['wcpn_date_type'], $start_date, $end_date );
+
+               if ( is_wp_error( $report ) ) {
+                       add_settings_error( 'wcp-dashboard', $report->get_error_code(), $report->get_error_message() );
+               } else {
+                       header( 'Content-Type: text/csv' );
+                       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
+        *
+        * @return string | WP_Error
+        */
+       protected static function generate_payment_report( $date_type, $start_date, $end_date ) {
+               global $wpdb;
+
+               if ( ! in_array( $date_type, array( 'paid', 'created' ), true ) ) {
+                       return new WP_Error( 'wcpn_bad_date_type', 'Invalid date type.' );
+               }
+
+               if ( ! is_int( $start_date ) || ! is_int( $end_date ) ) {
+                       return new WP_Error( 'wcpn_bad_dates', 'Invalid start or end date.' );
+               }
+
+               $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',
+               );
+
+               $table_name = self::get_table_name();
+
+               $request_indexes = $wpdb->get_results( $wpdb->prepare( "
+                       SELECT *
+                       FROM   `{$table_name}`
+                       WHERE  `{$date_type}` BETWEEN %d AND %d",
+                       $start_date,
+                       $end_date
+               ) );
+
+               ob_start();
+               $report = fopen( 'php://output', 'w' );
+
+               fputcsv( $report, $column_headings );
+
+               foreach( $request_indexes as $index ) {
+                       fputcsv( $report, self::get_report_row( $index ) );
+               }
+
+               fclose( $report );
+               return ob_get_clean();
+       }
+
+       /**
+        * Gather all the request details needed for a row in the export file
+        *
+        * @param stdClass $index
+        *
+        * @return array
+        */
+       protected static function get_report_row( $index ) {
+               switch_to_blog( $index->blog_id );
+
+               $request          = get_post( $index->post_id );
+               $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' ); ?>
+
+                       <p>
+                               This form will supply a CSV file with payment requests matching the parameters you select below.
+                               For example, all requests that were <code>paid</code> between <code><?php echo esc_html( $last_month ); ?></code> and <code><?php echo esc_html( $today ); ?></code>.
+                       </p>
+
+                       <p>
+                               <label>
+                                       Date type:
+                                       <select name="wcpn_date_type">
+                                               <option value="created">created</option>
+                                               <option value="paid" selected>paid</option>
+                                       </select>
+                               </label>
+                       </p>
+
+                       <p>
+                               <label>
+                                       Start date:
+                                       <input type="date" name="wcpn_export_start_date" class="medium-text" value="<?php echo esc_attr( $last_month ); ?>" />
+                               </label>
+                       </p>
+
+                       <p>
+                               <label>
+                                       End date:
+                                       <input type="date" name="wcpn_export_end_date" class="medium-text" value="<?php echo esc_attr( $today ); ?>" />
+                               </label>
+                       </p>
+
+                       <?php submit_button( 'Export' ); ?>
+               </form>
+
+               <?php
+       }
+
+       /**
+        * Loads and initializes the list table object.
+        */
+       public static function pre_render_dashboard() {
+               require_once( __DIR__ . '/payment-requests-list-table.php' );
+
+               self::$list_table = new Payment_Requests_List_Table();
+       }
+
+       /**
+        * Returns the current active tab in the UI.
+        */
+       public static function get_current_tab() {
+               $tab = 'overdue';
+
+               if ( isset( $_REQUEST['wcp-section'] ) && in_array( $_REQUEST['wcp-section'], array( 'pending', 'overdue', 'paid', 'incomplete', 'export' ) ) ) {
+                       $tab = $_REQUEST['wcp-section'];
+               }
+
+               return $tab;
+       }
+
+       /**
+        * Renders available tabs.
+        */
+       public static function render_dashboard_tabs() {
+               $current_section = self::get_current_tab();
+               $sections = array(
+                       'overdue' => 'Overdue',
+                       'pending' => 'Pending',
+                       'paid'    => 'Paid',
+                       'incomplete' => __( 'Incomplete', 'wordcamporg' ),
+                       'export'     => __( 'Export', 'wordcamporg' ),
+               );
+
+               foreach ( $sections as $section_key => $section_caption ) {
+                       $active = $current_section === $section_key ? 'nav-tab-active' : '';
+                       $url = add_query_arg( array(
+                               'wcp-section' => $section_key,
+                               'page' => 'wcp-dashboard',
+                       ), network_admin_url( 'index.php' ) );
+                       echo '<a class="nav-tab ' . $active . '" href="' . esc_url( $url ) . '">' . esc_html( $section_caption ) . '</a>';
+               }
+       }
+
+       /**
+        * Currency Conversion
+        *
+        * @param string $from What currency are we selling.
+        * @param string $to What currency are we buying.
+        * @param float $amount How much we're selling.
+        *
+        * @return float Converted amount.
+        */
+       public static function convert_currency( $from, $to, $amount ) {
+               global $wpdb;
+
+               $from = strtolower( $from );
+               $to = strtolower( $to );
+               $cache_key = md5( sprintf( 'wcp-exchange-rate-%s:%s', $from, $to ) );
+
+               $rate = 0;
+               if ( false === ( $rate = get_transient( $cache_key ) ) ) {
+                       $url = 'https://query.yahooapis.com/v1/public/yql';
+                       $url = add_query_arg( 'format', 'json', $url );
+                       $url = add_query_arg( 'env', rawurlencode( 'store://datatables.org/alltableswithkeys' ), $url );
+                       $url = add_query_arg( 'q', rawurlencode( $wpdb->prepare( 'select * from yahoo.finance.xchange where pair = %s', $from . $to ) ), $url );
+
+                       $request = wp_remote_get( esc_url_raw( $url ) );
+                       $body = json_decode( wp_remote_retrieve_body( $request ), true );
+
+                       if ( ! empty( $body['query']['results']['rate']['Ask'] ) ) {
+                               $rate = floatval( $body['query']['results']['rate']['Ask'] );
+                       }
+
+                       set_transient( $cache_key, $rate, 24 * HOUR_IN_SECONDS );
+               }
+
+               if ( $rate < 0.0000000001 )
+                       return 0;
+
+               return $amount * $rate;
+       }
+}
</ins></span></pre></div>
<a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcamppaymentsnetworkincludespaymentrequestslisttablephpfromrev2255sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcamppaymentsnetworkincludesclasslisttablephp"></a>
<div class="copfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Copied: sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-payments-network/includes/payment-requests-list-table.php (from rev 2255, sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-payments-network/includes/class-list-table.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-list-table.php                                (rev 0)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-payments-network/includes/payment-requests-list-table.php  2016-01-08 20:42:19 UTC (rev 2260)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,224 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+
+/**
+ * This list table class handles the output of data
+ * in the Payment Network Dashboard. Use the set_view() method
+ * to set a specific view/filter for the data.
+ *
+ * Note: Uses switch_to_blog() excessively.
+ */
+class Payment_Requests_List_Table extends WP_List_Table {
+
+       /**
+        * Used by the parent class, returns an array of
+        * columns to display.
+        */
+       public function get_columns() {
+               return array(
+                       'payment' => 'Payment',
+                       'status' => 'Status',
+                       'category' => 'Category',
+                       'due' => 'Due',
+                       'amount' => 'Amount',
+                       'method' => 'Method',
+                       'attachments' => 'Attachments',
+               );
+       }
+
+       /**
+        * Tells which columns are sortable. The array values
+        * are the ones passed on to the orderby request argument.
+        */
+       public function get_sortable_columns() {
+               return array(
+                       'category' => 'category',
+                       'due' => 'due',
+                       'status' => 'status',
+                       'method' => 'method',
+               );
+       }
+
+       /**
+        * Outputs inline CSS to be used with the list table.
+        */
+       public function print_inline_css() {
+               ?>
+               <style>
+               #wcp-list-table .search-box {
+                       margin-top: 12px;
+               }
+
+               #wcp-list-table .manage-column.column-payment {
+                       width: 30%;
+               }
+
+               #wcp-list-table .manage-column {
+                       width: 10%;
+               }
+               </style>
+               <?php
+       }
+
+       /**
+        * Parses query arguments and queries the index table in the database.
+        */
+       public function prepare_items() {
+               global $wpdb;
+
+               $sql = sprintf( "SELECT SQL_CALC_FOUND_ROWS blog_id, post_id FROM `%s` WHERE 1=1 ", Payment_Requests_Dashboard::get_table_name() );
+               $view = Payment_Requests_Dashboard::get_current_tab();
+               $where = '';
+               $orderby = '';
+               $limit = '';
+
+               $per_page = 10;
+               $orderby = 'due';
+               $order = 'asc';
+
+               if ( 'overdue' == $view ) {
+                       $where .= $wpdb->prepare( " AND `status` = 'unpaid' AND `due` > 0 AND `due` <= %d ", time() );
+               } elseif ( 'pending' == $view ) {
+                       $where .= " AND `status` = 'unpaid' ";
+               } elseif ( 'paid' == $view ) {
+                       $where .= " AND `status` = 'paid' ";
+                       $orderby = 'created';
+                       $order = 'desc';
+               } elseif( 'incomplete' == $view ) {
+                       $where .= " AND `status` = 'incomplete' ";
+               }
+
+               if ( ! empty( $_REQUEST['s'] ) ) {
+                       $where .= $wpdb->prepare( " AND `keywords` LIKE %s ", '%' . $wpdb->esc_like( wp_unslash( $_REQUEST['s'] ) ) . '%' );
+               }
+
+               if ( ! empty( $_REQUEST['orderby'] ) && in_array( $_REQUEST['orderby'], array_values( $this->get_sortable_columns() ) ) )
+                       $orderby = $_REQUEST['orderby'];
+
+               if ( ! empty( $_REQUEST['order'] ) && in_array( $_REQUEST['order'], array( 'asc', 'desc' ) ) )
+                       $order = $_REQUEST['order'];
+
+               $orderby = sprintf( " ORDER BY `%s` %s ", $orderby, $order );
+
+               $paged = isset( $_REQUEST['paged'] ) ? absint( $_REQUEST['paged'] ) : 1;
+
+               $limit .= sprintf( " LIMIT %d OFFSET %d ", $per_page, $per_page * ( $paged - 1 ) );
+
+               $sql .= $where . $orderby . $limit;
+
+               $this->items = $wpdb->get_results( $sql );
+
+               $total_items = $wpdb->get_var( "SELECT FOUND_ROWS();" );
+               $this->set_pagination_args( array(
+                       'total_items' => $total_items,
+                       'total_pages' => ceil( $total_items / $per_page ),
+                       'per_page' => $per_page,
+               ) );
+               return;
+       }
+
+       /**
+        * Output a single row in the list table.
+        *
+        * Holy cow, switch to blog! Please note that all the
+        * column_* methods are being run in a switched context.
+        */
+       public function single_row( $item ) {
+               switch_to_blog( $item->blog_id );
+               $request = get_post( $item->post_id );
+               parent::single_row( $request );
+               restore_current_blog();
+       }
+
+       /**
+        * Return the payment column contents.
+        *
+        * Note: runs in a switch_to_blog() context.
+        */
+       public function column_payment( $request ) {
+               $edit_post_link = add_query_arg( array( 'post' => $request->ID, 'action' => 'edit' ), admin_url( 'post.php' ) );
+               $actions = array(
+                       'view-all' => sprintf( '<a href="%s" target="_blank">View All</a>', esc_url( admin_url( 'edit.php?post_type=wcp_payment_request' ) ) ),
+               );
+
+               return sprintf( '<a href="%s" class="row-title" target="_blank">%s</a>%s',
+                       esc_url( $edit_post_link ),
+                       esc_html( $request->post_title ),
+                       $this->row_actions( $actions )
+               );
+       }
+
+       /**
+        * Note: runs in a switch_to_blog() context.
+        */
+       public function column_status( $request ) {
+               return esc_html( ucwords( $request->post_status ) );
+       }
+
+       /**
+        * Note: runs in a switch_to_blog() context.
+        */
+       public function column_category( $request ) {
+               require_once( WP_PLUGIN_DIR . '/wordcamp-payments/classes/payment-request.php' );
+               $categories        = WCP_Payment_Request::get_payment_categories();
+               $selected_category = get_post_meta( $request->ID, '_camppayments_payment_category', true );
+
+               return isset( $categories[ $selected_category ] ) ? $categories[ $selected_category ] : '';
+       }
+
+       /**
+        * Note: runs in a switch_to_blog() context.
+        */
+       public function column_amount( $request ) {
+               $currency = get_post_meta( $request->ID, '_camppayments_currency', true );
+               $amount = get_post_meta( $request->ID, '_camppayments_payment_amount', true );
+
+               $amount = preg_replace( '#[^\d.-]+#', '', $amount );
+               $amount = floatval( $amount );
+
+               if ( strpos( $currency, 'null' ) === false && $amount ) {
+                       $output = sprintf( '%s&nbsp;%s', esc_html( number_format( $amount, 2 ) ), esc_html( $currency ) );
+
+                       if ( $currency != 'USD' ) {
+                               $usd_amount = Payment_Requests_Dashboard::convert_currency( $currency, 'usd', $amount );
+                               if ( $usd_amount )
+                                       $output .= sprintf( '<br />~&nbsp;%s&nbsp;USD', esc_html( number_format( $usd_amount, 2 ) ) );
+                       }
+
+                       return $output;
+               } elseif ( $amount ) {
+                       return esc_html( $amount );
+               }
+       }
+
+       /**
+        * Note: runs in a switch_to_blog() context.
+        */
+       public function column_due( $request ) {
+               $due = get_post_meta( $request->ID, '_camppayments_due_by', true );
+               return $due ? date( 'Y-m-d', $due ) : '';
+       }
+
+       /**
+        * Note: runs in a switch_to_blog() context.
+        */
+       public function column_method( $request ) {
+               $method = get_post_meta( $request->ID, '_camppayments_payment_method', true );
+               return esc_html( $method );
+       }
+
+       /**
+        * Note: runs in a switch_to_blog() context.
+        */
+       public function column_attachments( $request ) {
+               $attachments = get_children( array( 'post_parent' => $request->ID ) );
+               $attachments = array_map( 'wp_get_attachment_url', wp_list_pluck( $attachments, 'ID' ) );
+
+               $output = array();
+               foreach ( $attachments as $attachment ) {
+                       $output[] = sprintf( '<a href="%s" target="_blank" class="dashicons dashicons-media-default" title="%s"></a>',
+                               esc_url( $attachment ), esc_attr( $attachment ) );
+               }
+
+               return implode( '', $output );
+       }
+}
</ins></span></pre></div>
<a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcamppaymentsnetworkwordcamppaymentsnetworkphp"></a>
<div class="delfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Deleted: sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-payments-network/wordcamp-payments-network.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/wordcamp-payments-network.php   2016-01-08 20:11:47 UTC (rev 2259)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-payments-network/wordcamp-payments-network.php     2016-01-08 20:42:19 UTC (rev 2260)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1,542 +0,0 @@
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-<?php
-/*
-* Plugin Name: WordCamp Payments Network Dashboard
-* Plugin URI: http://wordcamp.org
-* Version: 1.0
-* Author: Automattic
-* Author URI: http://wordcamp.org
-* License: GPLv2 or later
-* Network: true
-*/
-
-class WordCamp_Payments_Network_Tools {
-       public static $list_table;
-       public static $db_version = 6;
-
-       /**
-        * Runs during plugins_loaded, doh.
-        */
-       public static function plugins_loaded() {
-               $current_site = get_current_site();
-
-               // Schedule the aggregate event only on the main blog in the network.
-               if ( get_current_blog_id() == $current_site->blog_id && ! wp_next_scheduled( 'wordcamp_payments_aggregate' ) )
-                       wp_schedule_event( time(), 'hourly', 'wordcamp_payments_aggregate' );
-
-               add_action( 'wordcamp_payments_aggregate', array( __CLASS__, 'aggregate' ) );
-               add_action( 'admin_enqueue_scripts',  array( __CLASS__, 'enqueue_assets' ) );
-               add_action( 'network_admin_menu', array( __CLASS__, 'network_admin_menu' ) );
-               add_action( 'init', array( __CLASS__, 'upgrade' ) );
-               add_action( 'init', array( __CLASS__, 'process_export_request' ) );
-
-               // Diff-based updates to the index.
-               add_action( 'save_post', array( __CLASS__, 'save_post' ) );
-               add_action( 'delete_post', array( __CLASS__, 'delete_post' ) );
-
-               if ( ! empty( $_GET['wcp-debug-network'] ) && current_user_can( 'manage_network' ) )
-                       add_action( 'admin_init', function() { do_action( 'wordcamp_payments_aggregate' ); }, 99 );
-       }
-
-       /**
-        * Returns the name of the custom table.
-        */
-       public static function get_table_name() {
-               global $wpdb;
-               return $wpdb->get_blog_prefix(0) . 'wordcamp_payments_index';
-       }
-
-       /**
-        * Upgrade routine, makes sure that our schema is up to date.
-        */
-       public static function upgrade() {
-               global $wpdb;
-
-               // Don't attempt to perform upgrades outside of the dashboard.
-               if ( ! is_admin() )
-                       return;
-
-               $current_version = get_site_option( 'wcp_network_db_version', 0 );
-               if ( version_compare( $current_version, self::$db_version, '>=' ) )
-                       return;
-
-               require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
-
-               $charset_collate = "DEFAULT CHARACTER SET {$wpdb->charset} COLLATE {$wpdb->collate}";
-               $sql = sprintf( "CREATE TABLE %s (
-                       id int(11) unsigned NOT NULL auto_increment,
-                       blog_id int(11) unsigned NOT NULL default '0',
-                       post_id int(11) unsigned NOT NULL default '0',
-                       created int(11) unsigned NOT NULL default '0',
-                       paid int(11) unsigned NOT NULL default '0',
-                       category varchar(255) NOT NULL default '',
-                       method varchar(255) NOT NULL default '',
-                       due int(11) unsigned NOT NULL default '0',
-                       status varchar(255) NOT NULL default '',
-                       keywords text NOT NULL default '',
-                       PRIMARY KEY  (id),
-                       KEY blog_post_id (blog_id, post_id),
-                       KEY due (due),
-                       KEY status (status)
-               ) %s;", self::get_table_name(), $charset_collate );
-
-               dbDelta( $sql );
-
-               update_site_option( 'wcp_network_db_version', self::$db_version );
-       }
-
-       /**
-        * Runs on a cron job, reads data from all sites in the network
-        * and builds an index table for future queries.
-        */
-       public static function aggregate() {
-               global $wpdb;
-
-               // Register the custom payment statuses so that we can filter posts to include only them, in order to exclude trashed posts
-               require_once( WP_PLUGIN_DIR . '/wordcamp-payments/classes/payment-request.php' );
-               WCP_Payment_Request::register_post_statuses();
-
-               // Truncate existing table.
-               $wpdb->query( sprintf( "TRUNCATE TABLE %s;", self::get_table_name() ) );
-
-               $blogs = $wpdb->get_col( $wpdb->prepare( "SELECT blog_id FROM `{$wpdb->blogs}` WHERE site_id = %d ORDER BY last_updated DESC LIMIT %d;", $wpdb->siteid, 1000 ) );
-               foreach ( $blogs as $blog_id ) {
-                       switch_to_blog( $blog_id );
-
-                       $paged = 1;
-                       while ( $requests = get_posts( array(
-                               'paged' => $paged++,
-                               'post_status' => array( 'paid', 'unpaid', 'incomplete' ),
-                               'post_type' => 'wcp_payment_request',
-                               'posts_per_page' => 20,
-                       ) ) ) {
-                               foreach ( $requests as $request ) {
-                                       $wpdb->insert( self::get_table_name(), self::prepare_for_index( $request ) );
-                               }
-                       }
-
-                       restore_current_blog();
-               }
-       }
-
-       /**
-        * Given a $request (could be a post_id) create an array that can
-        * be used with $wpdb->update() or $wpdb->insert() to add or update
-        * an index entry.
-        */
-       public static function prepare_for_index( $request ) {
-               $request = get_post( $request );
-               $categories = WCP_Payment_Request::get_payment_categories();
-
-               // All things search.
-               $keywords = array( $request->post_title );
-
-               $category_slug = get_post_meta( $request->ID, '_camppayments_payment_category', true );
-               if ( ! empty( $categories[ $category_slug ] ) )
-                       $keywords[] = $categories[ $category_slug ];
-
-               $payment_method = get_post_meta( $request->ID, '_camppayments_payment_method', true );
-               if ( ! empty( $payment_method ) )
-                       $keywords[] = $payment_method;
-
-               return array(
-                       'blog_id' => get_current_blog_id(),
-                       'post_id' => $request->ID,
-                       'created' => get_post_time( 'U', true, $request->ID ),
-                               // todo Sometimes this is empty. Core normally catches this (r8636), but misses in our case because we don't use drafts. #1350-meta might have the side-effect of solving this.
-                       'paid'    => absint( get_post_meta( $request->ID, '_camppayments_date_vendor_paid', true ) ),
-                       'due' => absint( get_post_meta( $request->ID, '_camppayments_due_by', true ) ),
-                       'status' => $request->post_status,
-                       'method' => $payment_method,
-                       'category' => $category_slug,
-                       'keywords' => json_encode( $keywords ),
-               );
-       }
-
-       /**
-        * Runs during save_post, make sure our index is up to date.
-        */
-       public static function save_post( $post_id ) {
-               global $wpdb;
-
-               $request = get_post( $post_id );
-               if ( 'wcp_payment_request' != $request->post_type )
-                       return;
-
-               $table_name = self::get_table_name();
-               $entry_id = $wpdb->get_var( $wpdb->prepare( "SELECT id FROM {$table_name} WHERE `blog_id` = %d AND `post_id` = %d LIMIT 1;", get_current_blog_id(), $request->ID ) );
-
-               // Insert or update this record.
-               if ( empty( $entry_id ) ) {
-                       $wpdb->insert( $table_name, self::prepare_for_index( $request ) );
-               } else {
-                       $wpdb->update( $table_name, self::prepare_for_index( $request ), array( 'id' => $entry_id ) );
-               }
-       }
-
-       /**
-        * Delete an index query when a request post has been deleted.
-        */
-       public static function delete_post( $post_id ) {
-               global $wpdb;
-
-               $request = get_post( $post_id );
-               if ( 'wcp_payment_request' != $request->post_type )
-                       return;
-
-               $table_name = self::get_table_name();
-               $wpdb->query( $wpdb->prepare( "DELETE FROM {$table_name} WHERE `blog_id` = %d AND `post_id` = %d LIMIT 1;", get_current_blog_id(), $request->ID ) );
-       }
-
-       /**
-        * Create a network admin menu item entry.
-        */
-       public static function network_admin_menu() {
-               $dashboard = add_dashboard_page( 'WordCamp Payments Dashboard', 'Payments', 'manage_network', 'wcp-dashboard', array( __CLASS__, 'render_dashboard' ) );
-               add_action( 'load-' . $dashboard, array( __CLASS__, 'pre_render_dashboard' ) );
-       }
-
-       /**
-        * 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' );
-               }
-       }
-
-       /**
-        * Renders the Dashboard - Payments screen.
-        */
-       public static function render_dashboard() {
-               ?>
-
-               <div class="wrap">
-                       <h1>WordCamp Payments Dashboard</h1>
-
-                       <?php settings_errors(); ?>
-
-                       <h3 class="nav-tab-wrapper"><?php self::render_dashboard_tabs(); ?></h3>
-
-                       <?php
-                               if ( 'export' == self::get_current_tab() ) {
-                                       self::render_export_tab();
-                               } else {
-                                       self::render_table_tabs();
-                               }
-                       ?>
-
-               </div> <!-- /wrap -->
-
-               <?php
-       }
-
-       /**
-        * Render the table tabs, like Overview, Pending, etc
-        */
-       protected static function render_table_tabs() {
-               ?>
-
-               <?php self::$list_table->print_inline_css(); ?>
-
-               <div id="wcp-list-table">
-                       <?php self::$list_table->prepare_items(); ?>
-
-                       <form id="posts-filter" action="" method="get">
-                               <input type="hidden" name="page" value="wcp-dashboard" />
-                               <input type="hidden" name="wcp-section" value="<?php echo esc_attr( self::get_current_tab() ); ?>" />
-                               <?php self::$list_table->search_box( __( 'Search Payments', 'wordcamporg' ), 'wcp' ); ?>
-                               <?php self::$list_table->display(); ?>
-                       </form>
-               </div>
-
-               <?php
-       }
-
-       /**
-        * 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;
-               }
-
-               $start_date = strtotime( $_POST['wcpn_export_start_date'] . ' 00:00:00' );
-               $end_date   = strtotime( $_POST['wcpn_export_end_date']   . ' 23:59:59' );
-               $filename   = sanitize_file_name( sprintf( 'wordcamp-payments-%s-to-%s.csv', date( 'Y-m-d', $start_date ), date( 'Y-m-d', $end_date ) ) );
-
-               $report = self::generate_payment_report( $_POST['wcpn_date_type'], $start_date, $end_date );
-
-               if ( is_wp_error( $report ) ) {
-                       add_settings_error( 'wcp-dashboard', $report->get_error_code(), $report->get_error_message() );
-               } else {
-                       header( 'Content-Type: text/csv' );
-                       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
-        *
-        * @return string | WP_Error
-        */
-       protected static function generate_payment_report( $date_type, $start_date, $end_date ) {
-               global $wpdb;
-
-               if ( ! in_array( $date_type, array( 'paid', 'created' ), true ) ) {
-                       return new WP_Error( 'wcpn_bad_date_type', 'Invalid date type.' );
-               }
-
-               if ( ! is_int( $start_date ) || ! is_int( $end_date ) ) {
-                       return new WP_Error( 'wcpn_bad_dates', 'Invalid start or end date.' );
-               }
-
-               $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',
-               );
-
-               $table_name = self::get_table_name();
-
-               $request_indexes = $wpdb->get_results( $wpdb->prepare( "
-                       SELECT *
-                       FROM   `{$table_name}`
-                       WHERE  `{$date_type}` BETWEEN %d AND %d",
-                       $start_date,
-                       $end_date
-               ) );
-
-               ob_start();
-               $report = fopen( 'php://output', 'w' );
-
-               fputcsv( $report, $column_headings );
-
-               foreach( $request_indexes as $index ) {
-                       fputcsv( $report, self::get_report_row( $index ) );
-               }
-
-               fclose( $report );
-               return ob_get_clean();
-       }
-
-       /**
-        * Gather all the request details needed for a row in the export file
-        *
-        * @param stdClass $index
-        *
-        * @return array
-        */
-       protected static function get_report_row( $index ) {
-               switch_to_blog( $index->blog_id );
-
-               $request          = get_post( $index->post_id );
-               $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' ); ?>
-
-                       <p>
-                               This form will supply a CSV file with payment requests matching the parameters you select below.
-                               For example, all requests that were <code>paid</code> between <code><?php echo esc_html( $last_month ); ?></code> and <code><?php echo esc_html( $today ); ?></code>.
-                       </p>
-
-                       <p>
-                               <label>
-                                       Date type:
-                                       <select name="wcpn_date_type">
-                                               <option value="created">created</option>
-                                               <option value="paid" selected>paid</option>
-                                       </select>
-                               </label>
-                       </p>
-
-                       <p>
-                               <label>
-                                       Start date:
-                                       <input type="date" name="wcpn_export_start_date" class="medium-text" value="<?php echo esc_attr( $last_month ); ?>" />
-                               </label>
-                       </p>
-
-                       <p>
-                               <label>
-                                       End date:
-                                       <input type="date" name="wcpn_export_end_date" class="medium-text" value="<?php echo esc_attr( $today ); ?>" />
-                               </label>
-                       </p>
-
-                       <?php submit_button( 'Export' ); ?>
-               </form>
-
-               <?php
-       }
-
-       /**
-        * Loads and initializes the list table object.
-        */
-       public static function pre_render_dashboard() {
-               require_once( __DIR__ . '/includes/class-list-table.php' );
-
-               self::$list_table = new WordCamp_Payments_Network_List_Table;
-       }
-
-       /**
-        * Returns the current active tab in the UI.
-        */
-       public static function get_current_tab() {
-               $tab = 'overdue';
-
-               if ( isset( $_REQUEST['wcp-section'] ) && in_array( $_REQUEST['wcp-section'], array( 'pending', 'overdue', 'paid', 'incomplete', 'export' ) ) ) {
-                       $tab = $_REQUEST['wcp-section'];
-               }
-
-               return $tab;
-       }
-
-       /**
-        * Renders available tabs.
-        */
-       public static function render_dashboard_tabs() {
-               $current_section = self::get_current_tab();
-               $sections = array(
-                       'overdue' => 'Overdue',
-                       'pending' => 'Pending',
-                       'paid'    => 'Paid',
-                       'incomplete' => __( 'Incomplete', 'wordcamporg' ),
-                       'export'     => __( 'Export', 'wordcamporg' ),
-               );
-
-               foreach ( $sections as $section_key => $section_caption ) {
-                       $active = $current_section === $section_key ? 'nav-tab-active' : '';
-                       $url = add_query_arg( array(
-                               'wcp-section' => $section_key,
-                               'page' => 'wcp-dashboard',
-                       ), network_admin_url( 'index.php' ) );
-                       echo '<a class="nav-tab ' . $active . '" href="' . esc_url( $url ) . '">' . esc_html( $section_caption ) . '</a>';
-               }
-       }
-
-       /**
-        * Currency Conversion
-        *
-        * @param string $from What currency are we selling.
-        * @param string $to What currency are we buying.
-        * @param float $amount How much we're selling.
-        *
-        * @return float Converted amount.
-        */
-       public static function convert_currency( $from, $to, $amount ) {
-               global $wpdb;
-
-               $from = strtolower( $from );
-               $to = strtolower( $to );
-               $cache_key = md5( sprintf( 'wcp-exchange-rate-%s:%s', $from, $to ) );
-
-               $rate = 0;
-               if ( false === ( $rate = get_transient( $cache_key ) ) ) {
-                       $url = 'https://query.yahooapis.com/v1/public/yql';
-                       $url = add_query_arg( 'format', 'json', $url );
-                       $url = add_query_arg( 'env', rawurlencode( 'store://datatables.org/alltableswithkeys' ), $url );
-                       $url = add_query_arg( 'q', rawurlencode( $wpdb->prepare( 'select * from yahoo.finance.xchange where pair = %s', $from . $to ) ), $url );
-
-                       $request = wp_remote_get( esc_url_raw( $url ) );
-                       $body = json_decode( wp_remote_retrieve_body( $request ), true );
-
-                       if ( ! empty( $body['query']['results']['rate']['Ask'] ) ) {
-                               $rate = floatval( $body['query']['results']['rate']['Ask'] );
-                       }
-
-                       set_transient( $cache_key, $rate, 24 * HOUR_IN_SECONDS );
-               }
-
-               if ( $rate < 0.0000000001 )
-                       return 0;
-
-               return $amount * $rate;
-       }
-}
-
-// Initialize the plugin.
-add_action( 'plugins_loaded', array( 'WordCamp_Payments_Network_Tools', 'plugins_loaded' ) );
</del><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of file
</span></span></pre>
</div>
</div>

</body>
</html>