<!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>[7734] sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports: WordCamp Report: Add reporting for meetup applications.</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 { white-space: pre-line; 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/7734">7734</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/7734","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>vedjain</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2018-10-16 17:30:06 +0000 (Tue, 16 Oct 2018)</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 Report: Add reporting for meetup applications.

This commit adds functionality for downloading meetup application reports from admin dashboard. Also refactors WordCamp report classes
to make code DRY.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampreportsclassesreportclassbasephp">sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/classes/report/class-base.php</a></li>
<li><a href="#sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampreportsclassesreportclasswordcampdetailsphp">sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/classes/report/class-wordcamp-details.php</a></li>
<li><a href="#sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampreportsclassesreportclasswordcampstatusphp">sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/classes/report/class-wordcamp-status.php</a></li>
<li><a href="#sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampreportsindexphp">sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/index.php</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li><a href="#sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampreportsclassesreportclassbasedetailsphp">sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/classes/report/class-base-details.php</a></li>
<li><a href="#sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampreportsclassesreportclassbasestatusphp">sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/classes/report/class-base-status.php</a></li>
<li><a href="#sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampreportsclassesreportclassmeetupdetailsphp">sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/classes/report/class-meetup-details.php</a></li>
<li><a href="#sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampreportsclassesreportclassmeetupstatusphp">sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/classes/report/class-meetup-status.php</a></li>
<li><a href="#sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampreportsviewshtmlmeetupstatusphp">sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/views/html/meetup-status.php</a></li>
<li><a href="#sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampreportsviewspublicmeetupstatusphp">sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/views/public/meetup-status.php</a></li>
<li><a href="#sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampreportsviewsreportmeetupdetailsphp">sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/views/report/meetup-details.php</a></li>
<li><a href="#sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampreportsviewsreportmeetupstatusphp">sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/views/report/meetup-status.php</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampreportsclassesreportclassbasedetailsphp"></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-reports/classes/report/class-base-details.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-reports/classes/report/class-base-details.php                            (rev 0)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/classes/report/class-base-details.php      2018-10-16 17:30:06 UTC (rev 7734)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,363 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/**
+ * Implements base class for event reports which allows to export CSV.
+ *
+ * @package WordCamp\Reports
+ */
+
+namespace WordCamp\Reports\Report;
+defined( 'WPINC' ) || die();
+use Exception;
+use WordCamp\Reports\Utility\Date_Range;
+use function WordCamp\Reports\{get_assets_url, get_assets_dir_path, get_views_dir_path};
+use function WordCamp\Reports\Validation\{validate_date_range};
+use WordCamp\Utilities\Export_CSV;
+
+/**
+ * Class Base_Details
+ * Base class of details report type
+ *
+ * @package WordCamp\Reports\Report
+ */
+abstract class Base_Details extends Base {
+
+       /**
+        * A list of Event post IDs.
+        *
+        * @var array
+        */
+       public $event_ids = null;
+
+       /**
+        * Data fields that can be visible in a public context.
+        *
+        * @var array An associative array of key/default value pairs.
+        */
+       protected $public_data_fields = [];
+
+       /**
+        * Data fields that should only be visible in a private context.
+        *
+        * @var array An associative array of key/default value pairs.
+        */
+       protected $private_data_fields = [];
+
+       /**
+        * A date range that WordCamp events must fall within.
+        *
+        * @var null|Date_Range
+        */
+       public $range = null;
+
+       /**
+        * Base_Details constructor.
+        *
+        * @param Date_Range $date_range       Optional. A date range that WordCamp events must fall within.
+        * @param array      $event_ids     Optional. A list of Event post IDs to include in the results.
+        * @param array      $options          {
+        *     Optional. Additional report parameters.
+        *     See Base::__construct and the functions in WordCamp\Reports\Validation for additional parameters.
+        *
+        *     @type array $status_subset A list of valid status IDs.
+        *     @type array $fields        Not implemented yet.
+        * }
+        */
+       public function __construct( $date_range = null, $event_ids = null, $options = [] ) {
+
+               $options = wp_parse_args( $options, [
+                       'fields' => [],
+               ] );
+
+               parent::__construct( $options );
+
+               if ( ! is_null( $event_ids ) ) {
+                       $this->event_ids = $event_ids;
+               }
+
+               if ( $date_range instanceof Date_Range ) {
+                       $this->range = $date_range;
+               }
+
+               $this->public_data_fields = array_fill_keys( $this->get_public_data_fields(), '' );
+
+               $this->private_data_fields = array_fill_keys( $this->get_private_data_fields(), '' );
+
+               try {
+                       $this->options['fields'] = $this->validate_fields_input( $this->options['fields'] );
+               } catch ( Exception $e ) {
+                       $this->error->add(
+                               $this->slug . '-fields-error',
+                               $e->getMessage()
+                       );
+               }
+       }
+
+       /**
+        * Return fields that can be displayed in public context.
+        *
+        * @return array
+        */
+       abstract public function get_public_data_fields();
+
+       /**
+        * Return fields that can be viewed in private context.
+        *
+        * @return array
+        */
+       abstract public function get_private_data_fields();
+
+       /**
+        * Check the array of fields to include in the spreadsheet against the safelist of data fields.
+        *
+        * @param array $fields
+        *
+        * @return array The validated fields.
+        * @throws Exception
+        */
+       protected function validate_fields_input( array $fields ) {
+               $valid_fields = $this->get_data_fields_safelist();
+               $fields       = array_unique( $fields );
+
+               foreach ( $fields as $field ) {
+                       if ( ! array_key_exists( $field, $valid_fields ) ) {
+                               throw new Exception( sprintf(
+                                       'Invalid field: %s',
+                                       esc_html( $field )
+                               ) );
+                       }
+               }
+
+               return $fields;
+       }
+
+       /**
+        * Query and parse the data for the report.
+        *
+        * @return array
+        */
+       public function get_data() {
+               // Bail if there are errors.
+               if ( ! empty( $this->error->get_error_messages() ) ) {
+                       return array();
+               }
+
+               $data = [];
+
+               $event_posts = $this->get_event_posts();
+
+               foreach ( $event_posts as $post ) {
+                       $data[] = $this->fill_data_row( $post );
+               }
+
+               $this->filter_data_fields( $data );
+
+               // Reorder of the fields in each row.
+               $field_order = array_fill_keys( $this->get_field_order(), '' );
+               array_walk( $data, function( &$row ) use ( $field_order ) {
+                       $row = array_intersect_key( array_replace( $field_order, $row ), $row );
+               } );
+
+               return $data;
+       }
+
+       /**
+        * Get the values of all the relevant post meta keys for a Event post.
+        *
+        * @param \WP_Post $event
+        *
+        * @return array
+        */
+       public function fill_data_row( $event ) {
+               $meta_keys   = $this->get_meta_keys();
+
+               $row = [
+                       'ID'      => $event->ID,
+                       'Name'    => $event->post_title,
+                       'Created' => get_the_date( 'Y-m-d', $event->ID ),
+                       'Status'  => $event->post_status,
+               ];
+
+               foreach ( $meta_keys as $key ) {
+                       $row[ $key ] = get_post_meta( $event->ID, $key, true ) ? : '';
+               }
+
+               return $row;
+       }
+
+       /**
+        * Return array of WP_Post, for events that should be present in report.
+        *
+        * @return array
+        */
+       abstract public function get_event_posts();
+
+       /**
+        * Get the full list of fields in the order they should appear in.
+        *
+        * @return array
+        */
+       abstract static public function get_field_order();
+
+       /**
+        * Get a list of all the relevant meta keys for Event posts.
+        *
+        * @return array
+        */
+       abstract public function get_meta_keys();
+
+       /**
+        * Compile the report data into results.
+        *
+        * Currently unused.
+        *
+        * @param array $data The data to compile.
+        *
+        * @return array
+        */
+       public function compile_report_data( array $data ) {
+               return $data;
+       }
+
+       /**
+        * Render HTML form inputs for the fields that are available for inclusion in the spreadsheet.
+        *
+        * @param string $context        'public' or 'private'. Default 'public'.
+        * @param array  $field_defaults Optional. An associative array where the keys are field keys and the values
+        *                               are extra attributes for those field inputs. Examples: checked or required.
+        */
+       abstract static public function render_available_fields( $context = 'public', array $field_defaults = [] );
+
+       /**
+        * @param $shadow_report WordCamp_Details|Meetup_Details
+        * @param string $context
+        * @param array $field_defaults
+        */
+       public static function render_available_fields_in_report( $shadow_report, $context = 'public', array $field_defaults = [] ) {
+
+               $field_order      = array_fill_keys( $shadow_report->get_field_order(), '' );
+               $field_defaults   = array_replace( $field_order, $field_defaults );
+
+               $available_fields = array_intersect_key( $field_defaults, $shadow_report->get_data_fields_safelist() );
+               ?>
+               <fieldset class="fields-container">
+                       <legend class="fields-label">Available Fields</legend>
+
+                       <?php foreach ( $available_fields as $field_name => $extra_props ) : ?>
+                               <div class="field-checkbox">
+                                       <input
+                                               type="checkbox"
+                                               id="fields-<?php echo esc_attr( $field_name ); ?>"
+                                               name="fields[]"
+                                               value="<?php echo esc_attr( $field_name ); ?>"
+                                               <?php if ( $extra_props && is_string( $extra_props ) ) echo esc_html( $extra_props ); ?>
+                                       />
+                                       <label for="fields-<?php echo esc_attr( $field_name ); ?>">
+                                               <?php echo esc_attr( $field_name ); ?>
+                                       </label>
+                               </div>
+                       <?php endforeach; ?>
+               </fieldset>
+               <?php
+       }
+
+       /**
+        * Create an object of child class with relevant requirements passed to constructor
+        *
+        * @param string $context Can be 'public' or 'private'
+        *
+        * @return Base_Details
+        */
+       abstract static public function create_shadow_report_obj( $context );
+
+       /**
+        * Register all assets used by this report.
+        *
+        * @return void
+        */
+       public static function register_assets() {
+               wp_register_script(
+                       'wordcamp-details',
+                       get_assets_url() . 'js/' . 'wordcamp-details' . '.js',
+                       array(),
+                       filemtime( get_assets_dir_path() . 'js/' . 'wordcamp-details' . '.js' ),
+                       true
+               );
+
+               wp_register_style(
+                       'wordcamp-details',
+                       get_assets_url() . 'css/' . 'wordcamp-details' . '.css',
+                       array(),
+                       filemtime( get_assets_dir_path() . 'css/' . 'wordcamp-details' . '.css' ),
+                       'screen'
+               );
+       }
+
+       /**
+        * Enqueue JS and CSS assets for this report's admin interface.
+        *
+        * @return void
+        */
+       public static function enqueue_admin_assets() {
+               self::register_assets();
+
+               wp_enqueue_script( 'wordcamp-details' );
+               wp_enqueue_style( 'wordcamp-details' );
+       }
+
+       /**
+        * Render the page for this report in the WP Admin.
+        *
+        * @return void
+        */
+       abstract public static function render_admin_page();
+
+       /**
+        * Export the report data to a file.
+        *
+        * @return void
+        */
+       abstract public static function export_to_file();
+
+       /**
+        * Format the data for human-readable display.
+        *
+        * @param array $data The data to prepare.
+        *
+        * @return array
+        */
+       abstract public function prepare_data_for_display( array $data );
+
+       /**
+        * Export the report data to a file.
+        *
+        * @param $report Meetup_Details|WordCamp_Details
+        *
+        * @return void
+        */
+       public static function export_to_file_common( $report ) {
+               $filename = [ $report::$name ];
+               if ( $report->range instanceof Date_Range ) {
+                       $filename[] = $report->range->start->format( 'Y-m-d' );
+                       $filename[] = $report->range->end->format( 'Y-m-d' );
+               }
+               if ( isset( $report->include_counts ) && $report->include_counts ) {
+                       $filename[] = 'include-counts';
+               }
+
+               $data = $report->prepare_data_for_display( $report->get_data() );
+
+               $headers = ( ! empty( $data ) ) ? array_keys( $data[0] ) : [];
+
+               $exporter = new Export_CSV( array(
+                       'filename' => $filename,
+                       'headers'  => $headers,
+                       'data'     => $data,
+               ) );
+
+               if ( ! empty( $report->error->get_error_messages() ) ) {
+                       $exporter->error = $report->merge_errors( $report->error, $exporter->error );
+               }
+
+               $exporter->emit_file();
+       }
+}
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of file
</span><span class="cx" style="display: block; padding: 0 10px">Property changes on: sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/classes/report/class-base-details.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampreportsclassesreportclassbasestatusphp"></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-reports/classes/report/class-base-status.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-reports/classes/report/class-base-status.php                             (rev 0)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/classes/report/class-base-status.php       2018-10-16 17:30:06 UTC (rev 7734)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,186 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/**
+ * Implement base class for Status Reports
+ *
+ * @package WordCamp\Reports
+ */
+
+namespace WordCamp\Reports\Report;
+defined( 'WPINC' ) || die();
+
+use function WordCamp\Reports\{get_assets_url, get_assets_dir_path};
+use function WordCamp\Reports\Time\{convert_time_period_to_date_range};
+use Exception;
+use DateTime;
+use WP_Error;
+
+/**
+ * Class Base_Status
+ * Base class for status reports (egs Meetup, WordCamps)
+ *
+ * @package WordCamp\Reports\Report
+ */
+abstract class Base_Status extends Base {
+
+       /**
+        * Base_Status constructor.
+        *
+        * @param array $options
+        */
+       public function __construct( array $options = array() ) {
+               parent::__construct( $options );
+       }
+
+       /**
+        * Register all assets used by this report.
+        *
+        * @return void
+        */
+       protected static function register_assets() {
+               wp_register_script(
+                       'wordcamp-status',
+                       get_assets_url() . 'js/wordcamp-status.js',
+                       array( 'jquery', 'select2' ),
+                       filemtime( get_assets_dir_path() . 'js/wordcamp-status.js' ),
+                       true
+               );
+
+               wp_register_style(
+                       'wordcamp-status',
+                       get_assets_url() . 'css/wordcamp-status.css',
+                       array( 'select2' ),
+                       filemtime( get_assets_dir_path() . 'css/wordcamp-status.css' ),
+                       'screen'
+               );
+
+               Base_Details::enqueue_admin_assets();
+       }
+
+       /**
+        * Helper function to sort _status_change logs
+        *
+        * @param $logs
+        * @return array
+        */
+       public function sort_logs( $logs ) {
+               if ( ! empty( $logs ) ) {
+                       usort( $logs, function ( $a, $b ) {
+                               if ( $a['timestamp'] === $b['timestamp'] ) {
+                                       return 0;
+                               }
+                               return ( $a['timestamp'] > $b['timestamp'] ) ? 1 : -1;
+                       } );
+               }
+               return $logs;
+       }
+
+       /**
+        * Determine the ending status of a particular status change event.
+        *
+        * E.g. for this event:
+        *
+        *     Needs Vetting → More Info Requested
+        *
+        * The ending status would be "More Info Requested".
+        *
+        * @param array $log_entry A status change log entry.
+        * @param array $status_list List of status_id -> status_name mapping
+
+        *
+        * @return string
+        */
+       protected function get_log_status_result( $log_entry, $status_list ) {
+               if ( isset( $log_entry['message'] ) ) {
+                       $pieces = explode( ' &rarr; ', $log_entry['message'] );
+
+                       if ( isset( $pieces[1] ) ) {
+                               return $this->get_status_id_from_name( $pieces[1], $status_list );
+                       }
+               }
+
+               return '';
+       }
+
+
+       /**
+        * Given the ID of a WordCamp status, determine the ID string.
+        *
+        * @param string $status_name A WordCamp status name.
+        * @param array $status_list List of status_id -> status_name mapping
+        *
+        * @return string
+        */
+       protected function get_status_id_from_name( $status_name, $status_list ) {
+               $statuses = array_flip( $status_list );
+
+               if ( isset( $statuses[ $status_name ] ) ) {
+                       return $statuses[ $status_name ];
+               }
+
+               return '';
+       }
+
+       /**
+        * Enqueue JS and CSS assets for this report's admin interface.
+        *
+        * @return void
+        */
+       public static function enqueue_admin_assets() {
+               self::register_assets();
+               wp_enqueue_style( WordCamp_Details::$slug );
+               wp_enqueue_script( 'wordcamp-status' );
+               wp_enqueue_style( 'wordcamp-status' );
+       }
+
+       /**
+        * Parse input params for public report.
+        *
+        * @return array|null
+        */
+       public static function parse_public_report_input() {
+
+               $action = filter_input( INPUT_GET, 'action' );
+
+               // Apparently 'year' is a reserved URL parameter on the front end, so we prepend 'report-'.
+               $year   = filter_input( INPUT_GET, 'report-year', FILTER_VALIDATE_INT );
+               $period = filter_input( INPUT_GET, 'period' );
+               $status = filter_input( INPUT_GET, 'status' );
+
+               if ( ! $year ) {
+                       $year = absint( date( 'Y' ) );
+               }
+
+               if ( ! $period ) {
+                       $period = absint( date( 'm' ) );
+               }
+
+               $report  = null;
+               $error   = null;;
+               $options = [];
+               $range    = null;
+               if ( 'Show results' === $action ) {
+                       try {
+                               $range = convert_time_period_to_date_range( $year, $period );
+                       } catch ( Exception $e ) {
+                               $error = array(
+                                       'error' => new WP_Error( 'time-period-error', $e->getMessage() ),
+                               );
+                       }
+
+                       $options = array(
+                               'earliest_start' => new DateTime( '2015-01-01' ), // No status log data before 2015.
+                       );
+               }
+
+               return array(
+                       'range'   => $range,
+                       'status'  => $status,
+                       'options' => $options,
+                       'period'  => $period,
+                       'year'    => $year,
+                       'error'   => $error,
+               );
+       }
+
+
+}
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of file
</span><span class="cx" style="display: block; padding: 0 10px">Property changes on: sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/classes/report/class-base-status.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampreportsclassesreportclassbasephp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/classes/report/class-base.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-reports/classes/report/class-base.php    2018-10-16 17:09:51 UTC (rev 7733)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/classes/report/class-base.php      2018-10-16 17:30:06 UTC (rev 7734)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -134,21 +134,6 @@
</span><span class="cx" style="display: block; padding: 0 10px">        public abstract function compile_report_data( array $data );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         * Determine the data fields safelist based on the context of the report.
-        *
-        * @return array The list of fields that are safe to include.
-        */
-       protected function get_data_fields_safelist() {
-               $safelist = $this->public_data_fields;
-
-               if ( false === $this->options['public'] ) {
-                       $safelist = array_merge( $safelist, $this->private_data_fields );
-               }
-
-               return $safelist;
-       }
-
-       /**
</del><span class="cx" style="display: block; padding: 0 10px">          * Filter the report data prior to caching and compiling.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @param array $data The data to filter.
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -314,4 +299,19 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                return new \WP_REST_Response( $response_data );
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+       /**
+        * Determine the data fields safelist based on the context of the report.
+        *
+        * @return array The list of fields that are safe to include.
+        */
+       public function get_data_fields_safelist() {
+               $safelist = $this->public_data_fields;
+
+               if ( false === $this->options['public'] ) {
+                       $safelist = array_merge( $safelist, $this->private_data_fields );
+               }
+
+               return $safelist;
+       }
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span></span></pre></div>
<a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampreportsclassesreportclassmeetupdetailsphp"></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-reports/classes/report/class-meetup-details.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-reports/classes/report/class-meetup-details.php                          (rev 0)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/classes/report/class-meetup-details.php    2018-10-16 17:30:06 UTC (rev 7734)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,296 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/**
+ * Implements class for exporting meetup event details in CSV.
+ *
+ * @package WordCamp\Reports
+ */
+
+namespace WordCamp\Reports\Report;
+defined( 'WPINC' ) || die();
+use function WordCamp\Reports\{get_views_dir_path};
+use Meetup_Admin;
+
+/**
+ * Class Meetup_Details
+ *
+ * A report class for exporting a spreadsheet of Meetups.
+ *
+ * @package WordCamp\Reports\Report
+ */
+class Meetup_Details extends Base_Details {
+
+       /**
+        * Report name.
+        *
+        * @var string
+        */
+       public static $name = 'Meetup Details';
+
+       /**
+        * Report slug.
+        *
+        * @var string
+        */
+       public static $slug = 'meetup-details';
+
+       /**
+        * Report description.
+        *
+        * @var string
+        */
+       public static $description = 'Create a spreadsheet of details about Meetups that match optional criteria.';
+
+       /**
+        * Report methodology.
+        *
+        * @var string
+        */
+       public static $methodology = "
+               <ol>
+                       <li>Fetch all meetup posts.</li>
+                       <li>Extract the data for each post that match the fields requested.</li>
+                       <li>Walk through all of the extracted data and format it for display.</li>
+               </ol>
+       ";
+
+       /**
+        * Report group.
+        *
+        * @var string
+        */
+       public static $group = 'meetup';
+
+       /**
+        * Meetup_Details constructor.
+        *
+        * @param Date_Range $date_range       Optional. A date range that Meetup events must fall within.
+        * @param array      $meetup_ids     Optional. A list of Meetup post IDs to include in the results.
+        * @param array      $options          {
+        *     Optional. Additional report parameters.
+        *     See Base::__construct and the functions in WordCamp\Reports\Validation for additional parameters.
+        *
+        *     @type array $status_subset A list of valid status IDs.
+        *     @type array $fields        Not implemented yet.
+        * }
+        */
+       public function __construct( Date_Range $date_range = null, $meetup_ids = null, array $options = [] ) {
+               // Report-specific options.
+               $options = wp_parse_args( $options, [
+                       'fields' => [],
+               ] );
+
+               parent::__construct( $date_range, $meetup_ids, $options );
+       }
+
+       /**
+        * Return fields that can be displayed in public context.
+        *
+        * @return array
+        */
+       public function get_public_data_fields() {
+               return array_merge(
+                       [
+                               'Name',
+                       ],
+                       Meetup_Admin::get_public_meta_keys()
+               );
+       }
+
+       /**
+        * Return fields that can be viewed in private context.
+        *
+        * @return array
+        */
+       public function get_private_data_fields() {
+               return array_merge(
+                       [
+                               'ID',
+                               'Created',
+                               'Status',
+                               'Primary organizer WordPress.org username',
+                               'Co-Organizers usernames (seperated by comma)',
+                               'Number of past meetups',
+                               'Last meetup RSVP count',
+                       ],
+                       array_keys( Meetup_Admin::meta_keys( 'organizer' ) ),
+                       array_keys( $this->get_public_data_fields() )
+               );
+       }
+
+       /**
+        * Render the page for this report in the WP Admin.
+        *
+        * @return void
+        */
+       public static function render_admin_page() {
+               $field_defaults = [
+                       'ID' => 'checked',
+                       'Name' => 'checked disabled',
+                       'Created' => 'checked',
+                       'Status' => 'checked',
+               ];
+
+               include get_views_dir_path() . 'report/meetup-details.php';
+       }
+
+       /**
+        * Get Meetup posts that fit the report criteria.
+        *
+        * @return array An array of WP_Post objects.
+        */
+       public function get_event_posts() {
+               $post_args = array(
+                       'post_type'           => WCPT_MEETUP_SLUG,
+                       'post_status'         => 'any',
+                       'posts_per_page'      => -1,
+                       'nopaging'            => true,
+                       'no_found_rows'       => false,
+                       'ignore_sticky_posts' => true,
+                       'orderby'             => 'id',
+                       'order'               => 'ASC',
+               );
+
+               if ( $this->range instanceof Date_Range ) {
+                       // This replaces the default meta query.
+                       $post_args['meta_query'] = [
+                               [
+                                       'key'      => 'Start Date (YYYY-mm-dd)',
+                                       'value'    => array( $this->range->start->getTimestamp(), $this->range->end->getTimestamp() ),
+                                       'compare'  => 'BETWEEN',
+                                       'type'     => 'NUMERIC',
+                               ],
+                       ];
+                       $post_args['orderby'] = 'meta_value_num title';
+               }
+
+               if ( ! is_null( $this->event_ids ) ) {
+                       if ( empty( $this->event_ids ) ) {
+                               return [];
+                       }
+                       $post_args['post__in'] = $this->event_ids;
+               }
+
+               if ( $this->options['public'] ) {
+                       $post_args['post_status'] = \Meetup_Loader::get_public_post_statuses();
+               }
+
+               return get_posts( $post_args );
+       }
+
+       /**
+        * Get a list of all the relevant meta keys for Meetup posts.
+        *
+        * @return array
+        */
+       public function get_meta_keys() {
+         return array_keys( \Meetup_Admin::meta_keys( 'all' ) );
+       }
+
+       /**
+        * Get the full list of fields in the order they should appear in.
+        *
+        * @return array
+        */
+       static public function get_field_order() {
+               return array_merge(
+                       array('ID', 'name' ),
+                       array_keys( Meetup_Admin::meta_keys( 'information') ),
+                       array(
+                               'Status',
+                       ),
+                       array_keys( Meetup_Admin::meta_keys( 'organizer' ) )
+               );
+       }
+
+       /**
+        * Create an object of this class with relevant requirements passed to constructor
+        *
+        * @param string $context Can be 'public' or 'private'
+        *
+        * @return Base_Details
+        */
+       static public function create_shadow_report_obj( $context ) {
+               return new self( null, [], ['public' => 'public' === $context ] );
+       }
+
+       /**
+        * Render list of fields that can be present in exported CSV.
+        *
+        * @param string $context
+        * @param array $field_defaults
+        */
+       static public function render_available_fields( $context = 'public', array $field_defaults = [] ) {
+               $shadow_report = self::create_shadow_report_obj( $context );
+               self::render_available_fields_in_report( $shadow_report, $context, $field_defaults );
+       }
+
+       /**
+        * Export the report data to a file.
+        *
+        * @return void
+        */
+       public static function export_to_file( ) {
+
+               $fields           = filter_input( INPUT_POST, 'fields', FILTER_SANITIZE_STRING, [ 'flags' => FILTER_REQUIRE_ARRAY ] );
+               $action           = filter_input( INPUT_POST, 'action' );
+               $nonce            = filter_input( INPUT_POST, self::$slug . '-nonce' );
+
+               $report = null;
+
+               if ( 'Export CSV' !== $action ) {
+                       return;
+               }
+
+               if ( ! wp_verify_nonce( $nonce, 'run-report' ) && current_user_can( 'manage_network' ) ) {
+                       return;
+               }
+
+               $error = null;
+               $range = null;
+
+               // The "Name" field should always be included, but does not get submitted because the input is disabled,
+               // so add it in here.
+               $fields[] = 'Name';
+
+               $options = array(
+                       'fields' => $fields,
+                       'public' => false,
+               );
+               $report = new self( $range, null, $options );
+
+               self::export_to_file_common( $report );
+       }
+
+       /**
+        * Format the data for human-readable display.
+        *
+        * @param array $data The data to prepare.
+        *
+        * @return array
+        */
+       public function prepare_data_for_display( array $data ) {
+               $all_statuses = Meetup_Admin::get_post_statuses();
+               array_walk( $data, function( &$row ) use ( $all_statuses ) {
+                       foreach ( $row as $key => $value ) {
+                               if ( ! in_array( $key, $this->options['fields'], true ) ) {
+                                       unset( $row[ $key ] );
+                                       continue;
+                               }
+                               switch ( $key ) {
+                                       case 'Status':
+                                               $row[ $key ] = $all_statuses[ $value ];
+                                               break;
+                                       case 'Meetup Co-organizer names':
+                                               if ( is_array( $value ) ) {
+                                                       $org_names = wp_list_pluck( $value, 'name' );
+                                                       $row[ $key ] = implode( ', ', $org_names );
+                                               }
+                                               break;
+                               }
+                       }
+               } );
+
+               return $data;
+       }
+
+}
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of file
</span><span class="cx" style="display: block; padding: 0 10px">Property changes on: sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/classes/report/class-meetup-details.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampreportsclassesreportclassmeetupstatusphp"></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-reports/classes/report/class-meetup-status.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-reports/classes/report/class-meetup-status.php                           (rev 0)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/classes/report/class-meetup-status.php     2018-10-16 17:30:06 UTC (rev 7734)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,406 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/**
+ * Implements class for generating meetup report that allows to filter with status.
+ *
+ * @package WordCamp\Reports
+ */
+
+namespace WordCamp\Reports\Report;
+defined( 'WPINC' ) || die();
+
+use Exception;
+use function WordCamp\Reports\Time\{year_array, quarter_array, month_array};
+use function WordCamp\Reports\{get_views_dir_path};
+use function WordCamp\Reports\Validation\{validate_date_range};
+use WordPress_Community\Applications\Meetup_Application;
+
+/**
+ * Class Meetup_Status
+ *
+ * A report class for generating a snapshot of Meetup status activity during a specified date range
+ */
+class Meetup_Status extends Base_Status {
+
+       /**
+        * Report name.
+        *
+        * @var string
+        */
+       public static $name = 'Meetup Status';
+
+       /**
+        * Report slug.
+        *
+        * @var string
+        */
+       public static $slug = 'meetup-status';
+
+       /**
+        * Report description.
+        *
+        * @var string
+        */
+       public static $description = 'Meetup application status changes during a given time period';
+
+       /**
+        * Report methodology.
+        *
+        * @var string
+        */
+       public static $methodology = "
+       Retrieve all Meetup posts which have status change in given time range.
+";
+
+       /**
+        * A container object to hold error messages.
+        *
+        * @var \WP_Error
+        */
+       public $error = null;
+
+       /**
+        * Report group.
+        *
+        * @var string
+        */
+       public static $group = 'meetup';
+
+       /**
+        * Shortcode tag for outputting the public report from
+        *
+        * @var string
+        */
+       public static $shortcode_tag = 'meetup_status_report';
+
+       /**
+        * The date range that defines the scope of the report data.
+        *
+        * @var null|Date_Range
+        */
+       public $range = null;
+
+       /**
+        * The status to filter for in the report
+        *
+        * @var string
+        */
+       public $status = 'any';
+
+       /**
+        * Data fields that can be visible in a public context.
+        *
+        * @var array An associative array of key/default value pairs.
+        */
+       protected $public_data_fields = array(
+               'name'          => '',
+               'logs'          => array(),
+               'latest_log'    => '',
+               'latest_status' => '',
+       );
+
+       /**
+        * Meetup_Status constructor.
+        *
+        * @param string $start_date
+        * @param string $end_date
+        * @param string $status
+        * @param array $options
+        */
+       public function __construct( $start_date, $end_date, $status = '', array $options = array() ) {
+
+               parent::__construct( $options );
+
+               try {
+                       $this->range = validate_date_range( $start_date, $end_date, $options );
+               } catch ( Exception $e ) {
+                       $this->error->add(
+                               self::$slug . '-date-error',
+                               $e->getMessage()
+                       );
+               }
+               $this->status = $status;
+       }
+
+       /**
+        * Generate a cache key.
+        *
+        * @return string
+        */
+       protected function get_cache_key() {
+               $cache_key_segments = [
+                       parent::get_cache_key(),
+                       $this->range->generate_cache_key_segment(),
+               ];
+
+               if ( $this->status ) {
+                       $cache_key_segments[] = $this->status;
+               }
+
+               return implode( '_', $cache_key_segments );
+       }
+
+       /**
+        * Generate a cache expiration interval.
+        *
+        * @return int A time interval in seconds.
+        */
+       protected function get_cache_expiration() {
+               return $this->range->generate_cache_duration( parent::get_cache_expiration() );
+       }
+
+       /**
+        * Retrieve log data for Meetup posts matching date range and status.
+        *
+        * @return array|mixed|null
+        */
+       public function get_data() {
+               if ( ! empty( $this->error->get_error_messages() ) ) {
+                       return array();
+               }
+
+               // Maybe use cached data.
+               $data = $this->maybe_get_cached_data();
+               if ( is_array( $data ) ) {
+                       return $data;
+               }
+
+               $meetup_posts = $this->get_meetup_posts();
+               $data         = array();
+
+               // Ensure status labels can match status log messages.
+               add_filter( 'locale', array( $this, 'set_locale_to_en_US' ) );
+
+               foreach ( $meetup_posts as $meetup ) {
+                       $logs = $this->sort_logs( get_post_meta( $meetup->ID, '_status_change' ) );
+
+                       if ( empty( $logs ) ) {
+                               continue;
+                       }
+
+                       // Get logs within the time range and status
+                       $filtered_logs = array_filter( $logs, function ( $entry ) {
+                               return (
+                                       $entry['timestamp'] >= $this->range->start->getTimestamp()
+                                       &&
+                                       $entry['timestamp'] <= $this->range->end->getTimestamp()
+                                       &&
+                                       (
+                                               'any' === $this->status
+                                               ||
+                                               $this->get_log_status_result( $entry, Meetup_Application::get_post_statuses() ) === $this->status
+                                       )
+                               );
+                       } ) ;
+
+                       if ( empty( $filtered_logs ) ) {
+                               continue;
+                       }
+
+                       $latest_log = end( $logs );
+                       $data[ $meetup->ID ] = array(
+                               'name' => get_the_title( $meetup ),
+                               'logs' => $logs,
+                               'latest_log' => $latest_log,
+                               'latest_status' => $this->get_log_status_result( $latest_log, Meetup_Application::get_post_statuses() ),
+                       );
+               }
+
+               // Remove the temporary locale change.
+               remove_filter( 'locale', array( $this, 'set_locale_to_en_US' ) );
+
+               $data = $this->filter_data_fields( $data );
+               $this->maybe_cache_data( $data );
+
+               return $data;
+       }
+
+       /**
+        * Get all Meetup posts which have status changed between given time fram
+        *
+        * @return array
+        */
+       protected function get_meetup_posts() {
+               global $wpdb;
+               $meetup_post_type = WCPT_MEETUP_SLUG;
+               $meetup_post_objs = $wpdb->get_results(
+                       $wpdb->prepare(
+                               "
+                       SELECT DISTINCT post_id
+                       FROM {$wpdb->prefix}postmeta
+                       WHERE 
+                               meta_key LIKE '_status_change_log_$meetup_post_type%'
+                       AND
+                               meta_value >= %d
+                       AND 
+                               meta_value <= %d
+                       ",
+                               $this->range->start->getTimestamp(),
+                               $this->range->end->getTimestamp()
+                       )
+               );
+               $meetup_post_ids = wp_list_pluck( $meetup_post_objs, 'post_id' );
+               $post_args = array(
+                       'post_status' => array_keys( \Meetup_Admin::get_post_statuses() ),
+                       'post_type' => WCPT_MEETUP_SLUG,
+                       'posts_per_page' => -1,
+                       'post__in' => $meetup_post_ids,
+                       'orderby' => 'date',
+                       'order' => 'ASC',
+               );
+               return get_posts( $post_args );
+       }
+
+       /**
+        * Return groups of data to be returned.
+        * @param array $data
+        *
+        * @return array
+        */
+       public function compile_report_data( array $data ) {
+               return array(
+                       'meetups' => $data,
+               );
+       }
+
+       /**
+        * Determine whether to render the public report form.
+        *
+        * This shortcode is limited to use on pages.
+        *
+        * @return string HTML content to display shortcode.
+        */
+       public static function handle_shortcode() {
+               $html = '';
+
+               if ( 'page' === get_post_type() ) {
+                       self::register_assets();
+
+                       wp_enqueue_style( 'select2' );
+                       wp_enqueue_script( 'wordcamp-status' );
+
+                       ob_start();
+                       self::render_public_page();
+                       $html = ob_get_clean();
+               }
+
+               return $html;
+       }
+
+       /**
+        * Render the page for this report on the front end.
+        *
+        * @return void
+        */
+       public static function render_public_page() {
+               $params = self::parse_public_report_input();
+               $years    = year_array( absint( date( 'Y' ) ), 2015 );
+               $quarters = quarter_array();
+               $months   = month_array();
+               $statuses = \Meetup_Admin::get_post_statuses();
+
+               $error = $params['error'];
+               $report = null;
+               $period = $params['period'];
+               $year = $params['year'];
+               $status = $params['status'];
+               if ( ! empty( $params )  && isset( $params['range'] ) ) {
+                       $report = new self( $params['range']->start, $params['range']->end, $params['status'], $params['options'] );
+               }
+
+               include get_views_dir_path() . 'public/meetup-status.php';
+       }
+
+       /**
+        * Render the page for this report in the WP Admin.
+        *
+        * @return void
+        */
+       public static function render_admin_page() {
+               $start_date = filter_input( INPUT_POST, 'start-date' );
+               $end_date   = filter_input( INPUT_POST, 'end-date' );
+               $status     = filter_input( INPUT_POST, 'status' );
+               $refresh    = filter_input( INPUT_POST, 'refresh', FILTER_VALIDATE_BOOLEAN );
+               $action     = filter_input( INPUT_POST, 'action' );
+               $nonce      = filter_input( INPUT_POST, self::$slug . '-nonce' );
+               $fields     = filter_input( INPUT_POST, 'fields', FILTER_SANITIZE_STRING, [ 'flags' => FILTER_REQUIRE_ARRAY ] );
+               $statuses   = Meetup_Application::get_post_statuses();
+
+               $field_defaults = [
+                       'ID'                      => 'checked',
+                       'Name'                    => 'checked disabled',
+                       'Start Date (YYYY-mm-dd)' => 'checked',
+                       'End Date (YYYY-mm-dd)'   => 'checked',
+                       'Location'                => 'checked',
+                       'URL'                     => 'checked',
+                       'Created'                 => 'checked',
+                       'Status'                  => 'checked',
+               ];
+
+               $report = null;
+
+               if ( wp_verify_nonce( $nonce, 'run-report' )
+                    && current_user_can( 'manage_network' )
+               ) {
+                       $options = array(
+                               'public' => false,
+                       );
+
+                       if ( $refresh ) {
+                               $options['flush_cache'] = true;
+                       }
+
+                       if ( 'Show results' === $action ) {
+                               // $report variable is used in meetup-status.php to render report.
+                               $report = new self( $start_date, $end_date, $status, $options );
+                       } elseif ( 'Export CSV' === $action ) {
+                               $status_report = new self( $start_date, $end_date, $status, $options );
+                               $meetup_ids = array_keys( $status_report->get_data() );
+                               $fields[] = 'Name';
+                               $options['fields'] = $fields;
+                               $detail_report = new Meetup_Details( null, $meetup_ids, $options );
+                               Meetup_Details::export_to_file_common( $detail_report );
+                               return;
+                       }
+               }
+               include get_views_dir_path() . 'report/meetup-status.php';
+       }
+
+       /**
+        * Render an HTML version of the report output.
+        *
+        * @return void
+        */
+       public function render_html() {
+               $data       = $this->compile_report_data( $this->get_data() );
+               $start_date = $this->range->start;
+               $end_date   = $this->range->end;
+               $status     = $this->status;
+
+               $meetups  = $data['meetups'];
+               $statuses = Meetup_Application::get_post_statuses();
+
+               if ( ! empty( $this->error->get_error_messages() ) ) {
+                       $this->render_error_html();
+               } else {
+                       include get_views_dir_path() . 'html/meetup-status.php';
+               }
+       }
+
+       /**
+        * Export the report data to a file.
+        *
+        * @return void
+        */
+       public static function export_to_file() {
+               $action = filter_input( INPUT_POST, 'action' );
+               $report = filter_input( INPUT_GET, 'report' );
+               if ( $report !== self::$slug ) {
+                       return;
+               }
+               if ( 'Export CSV' !== $action ) {
+                       return;
+               }
+
+               self::render_admin_page();
+       }
+}
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of file
</span><span class="cx" style="display: block; padding: 0 10px">Property changes on: sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/classes/report/class-meetup-status.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampreportsclassesreportclasswordcampdetailsphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/classes/report/class-wordcamp-details.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-reports/classes/report/class-wordcamp-details.php        2018-10-16 17:09:51 UTC (rev 7733)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/classes/report/class-wordcamp-details.php  2018-10-16 17:30:06 UTC (rev 7734)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -8,12 +8,11 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> use Exception;
</span><span class="cx" style="display: block; padding: 0 10px"> use DateTime;
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-use WP_Post, WP_Query, WP_Error;
-use function WordCamp\Reports\{get_assets_url, get_assets_dir_path, get_views_dir_path};
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+use WP_Post, WP_Query;
+use function WordCamp\Reports\{get_views_dir_path};
</ins><span class="cx" style="display: block; padding: 0 10px"> use WordCamp\Reports\Utility\Date_Range;
</span><span class="cx" style="display: block; padding: 0 10px"> use function WordCamp\Reports\Validation\{validate_date_range, validate_wordcamp_id};
</span><span class="cx" style="display: block; padding: 0 10px"> use WordCamp_Admin, WordCamp_Loader;
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-use WordCamp\Utilities\Export_CSV;
</del><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><span class="cx" style="display: block; padding: 0 10px">  * Class WordCamp_Details
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -25,7 +24,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">  *
</span><span class="cx" style="display: block; padding: 0 10px">  * @package WordCamp\Reports\Report
</span><span class="cx" style="display: block; padding: 0 10px">  */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-class WordCamp_Details extends Base {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+class WordCamp_Details extends Base_Details {
</ins><span class="cx" style="display: block; padding: 0 10px">         /**
</span><span class="cx" style="display: block; padding: 0 10px">         * Report name.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -68,20 +67,6 @@
</span><span class="cx" style="display: block; padding: 0 10px">        public static $group = 'wordcamp';
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         * A date range that WordCamp events must fall within.
-        *
-        * @var null|Date_Range
-        */
-       public $range = null;
-
-       /**
-        * A list of WordCamp post IDs.
-        *
-        * @var array
-        */
-       public $wordcamp_ids = [];
-
-       /**
</del><span class="cx" style="display: block; padding: 0 10px">          * Whether to include counts of various post types for each WordCamp.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @var bool
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -89,20 +74,6 @@
</span><span class="cx" style="display: block; padding: 0 10px">        public $include_counts = false;
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         * Data fields that can be visible in a public context.
-        *
-        * @var array An associative array of key/default value pairs.
-        */
-       protected $public_data_fields = [];
-
-       /**
-        * Data fields that should only be visible in a private context.
-        *
-        * @var array An associative array of key/default value pairs.
-        */
-       protected $private_data_fields = [];
-
-       /**
</del><span class="cx" style="display: block; padding: 0 10px">          * WordCamp_Details constructor.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @param Date_Range $date_range       Optional. A date range that WordCamp events must fall within.
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -117,44 +88,39 @@
</span><span class="cx" style="display: block; padding: 0 10px">         *     @type array $fields        Not implemented yet.
</span><span class="cx" style="display: block; padding: 0 10px">         * }
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        public function __construct( Date_Range $date_range = null, array $wordcamp_ids = [], $include_counts = false, array $options = [] ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ public function __construct( Date_Range $date_range = null, array $wordcamp_ids = null, $include_counts = false, array $options = [] ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                 // Report-specific options.
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $options = wp_parse_args( $options, [
-                       'fields' => [],
-               ] );
</del><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                parent::__construct( $options );
-
-               if ( $date_range instanceof Date_Range ) {
-                       $this->range = $date_range;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         parent::__construct( $date_range, $wordcamp_ids, $options );
+               if ( ! is_null( $wordcamp_ids ) ) {
+                       $this->event_ids = [];
+                       $this->validate_wordcamp_ids( $wordcamp_ids );
</ins><span class="cx" style="display: block; padding: 0 10px">                 }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                if ( ! empty( $wordcamp_ids ) ) {
-                       foreach ( $wordcamp_ids as $wordcamp_id ) {
-                               try {
-                                       $this->wordcamp_ids[] = validate_wordcamp_id( $wordcamp_id, [ 'require_site' => false ] )->post_id;
-                               } catch ( Exception $e ) {
-                                       $this->error->add(
-                                               self::$slug . '-wordcamp-id-error',
-                                               $e->getMessage()
-                                       );
-
-                                       break;
-                               }
-                       }
-               }
-
</del><span class="cx" style="display: block; padding: 0 10px">                 $this->include_counts = wp_validate_boolean( $include_counts );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+        }
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $public_data_field_keys = array_merge(
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ /**
+        * Return fields that can be displayed in public context.
+        *
+        * @return array
+        */
+       public function get_public_data_fields() {
+               return array_merge(
</ins><span class="cx" style="display: block; padding: 0 10px">                         [
</span><span class="cx" style="display: block; padding: 0 10px">                                'Name',
</span><span class="cx" style="display: block; padding: 0 10px">                        ],
</span><span class="cx" style="display: block; padding: 0 10px">                        WordCamp_Loader::get_public_meta_keys()
</span><span class="cx" style="display: block; padding: 0 10px">                );
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $this->public_data_fields = array_fill_keys( $public_data_field_keys, '' );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ }
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $private_data_field_keys = array_merge(
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ /**
+        * Return fields that can be viewed in private context.
+        *
+        * @return array
+        */
+       public function get_private_data_fields() {
+               return array_merge(
</ins><span class="cx" style="display: block; padding: 0 10px">                         [
</span><span class="cx" style="display: block; padding: 0 10px">                                'ID',
</span><span class="cx" style="display: block; padding: 0 10px">                                'Created',
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -164,93 +130,38 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                'Sponsors',
</span><span class="cx" style="display: block; padding: 0 10px">                                'Organizers',
</span><span class="cx" style="display: block; padding: 0 10px">                        ],
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        array_diff( $this->get_meta_keys(), array_keys( $this->public_data_fields ) )
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 array_diff( $this->get_meta_keys(), array_keys( $this->get_public_data_fields() ) )
</ins><span class="cx" style="display: block; padding: 0 10px">                 );
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $this->private_data_fields = array_fill_keys( $private_data_field_keys, '' );
-
-               try {
-                       $this->options['fields'] = $this->validate_fields_input( $this->options['fields'] );
-               } catch ( Exception $e ) {
-                       $this->error->add(
-                               self::$slug . '-fields-error',
-                               $e->getMessage()
-                       );
-               }
</del><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         * Check the array of fields to include in the spreadsheet against the safelist of data fields.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+  * Validate WordCamp ids and filter those without a site.
</ins><span class="cx" style="display: block; padding: 0 10px">          *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         * @param array $fields
-        *
-        * @return array The validated fields.
-        * @throws Exception
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+  * @param $wordcamp_ids
</ins><span class="cx" style="display: block; padding: 0 10px">          */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        protected function validate_fields_input( array $fields ) {
-               $valid_fields = $this->get_data_fields_safelist();
-               $fields       = array_unique( $fields );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ public function validate_wordcamp_ids( $wordcamp_ids ) {
+               if ( ! empty( $wordcamp_ids ) ) {
+                       foreach ( $wordcamp_ids as $wordcamp_id ) {
+                               try {
+                                       $this->event_ids[] = validate_wordcamp_id( $wordcamp_id, [ 'require_site' => false ] )->post_id;
+                               } catch ( Exception $e ) {
+                                       $this->error->add(
+                                               self::$slug . '-wordcamp-id-error',
+                                               $e->getMessage()
+                                       );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                foreach ( $fields as $field ) {
-                       if ( ! array_key_exists( $field, $valid_fields ) ) {
-                               throw new Exception( sprintf(
-                                       'Invalid field: %s',
-                                       esc_html( $field )
-                               ) );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                 break;
+                               }
</ins><span class="cx" style="display: block; padding: 0 10px">                         }
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-
-               return $fields;
</del><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         * Query and parse the data for the report.
-        *
-        * @return array
-        */
-       public function get_data() {
-               // Bail if there are errors.
-               if ( ! empty( $this->error->get_error_messages() ) ) {
-                       return array();
-               }
-
-               $data = [];
-
-               $wordcamp_posts = $this->get_wordcamp_posts();
-
-               foreach ( $wordcamp_posts as $post ) {
-                       $data[] = $this->fill_data_row( $post );
-               }
-
-               $data = $this->filter_data_fields( $data );
-
-               // Reorder of the fields in each row.
-               $field_order = array_fill_keys( self::get_field_order(), '' );
-               array_walk( $data, function( &$row ) use ( $field_order ) {
-                       $row = array_intersect_key( array_replace( $field_order, $row ), $row );
-               } );
-
-               return $data;
-       }
-
-       /**
-        * Compile the report data into results.
-        *
-        * Currently unused.
-        *
-        * @param array $data The data to compile.
-        *
-        * @return array
-        */
-       public function compile_report_data( array $data ) {
-               return $data;
-       }
-
-       /**
</del><span class="cx" style="display: block; padding: 0 10px">          * Get the full list of fields in the order they should appear in.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @return array
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        protected static function get_field_order() {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ public static function get_field_order() {
</ins><span class="cx" style="display: block; padding: 0 10px">                 /* @var WordCamp_Admin $wordcamp_admin */
</span><span class="cx" style="display: block; padding: 0 10px">                global $wordcamp_admin;
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -335,7 +246,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @return array An array of WP_Post objects.
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        protected function get_wordcamp_posts() {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ public function get_event_posts() {
</ins><span class="cx" style="display: block; padding: 0 10px">                 $post_args = array(
</span><span class="cx" style="display: block; padding: 0 10px">                        'post_type'           => WCPT_POST_TYPE_ID,
</span><span class="cx" style="display: block; padding: 0 10px">                        'post_status'         => 'any',
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -360,8 +271,8 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        $post_args['orderby'] = 'meta_value_num title';
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                if ( ! empty( $this->wordcamp_ids ) ) {
-                       $post_args['post__in'] = $this->wordcamp_ids;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         if ( ! empty( $this->event_ids ) ) {
+                       $post_args['post__in'] = $this->event_ids;
</ins><span class="cx" style="display: block; padding: 0 10px">                 }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                if ( $this->options['public'] ) {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -372,39 +283,11 @@
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         * Get the values of all the relevant post meta keys for a WordCamp post.
-        *
-        * @param WP_Post $wordcamp
-        *
-        * @return array
-        */
-       protected function fill_data_row( WP_Post $wordcamp ) {
-               $meta_keys   = $this->get_meta_keys();
-
-               $row = [
-                       'ID'      => $wordcamp->ID,
-                       'Name'    => $wordcamp->post_title,
-                       'Created' => get_the_date( 'Y-m-d', $wordcamp->ID ),
-                       'Status'  => $wordcamp->post_status,
-               ];
-
-               if ( $this->include_counts ) {
-                       $row = array_merge( $row, $this->get_counts( $wordcamp ) );
-               }
-
-               foreach ( $meta_keys as $key ) {
-                       $row[ $key ] = get_post_meta( $wordcamp->ID, $key, true ) ?: '';
-               }
-
-               return $row;
-       }
-
-       /**
</del><span class="cx" style="display: block; padding: 0 10px">          * Get a list of all the relevant meta keys for WordCamp posts.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @return array
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        protected function get_meta_keys() {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ public function get_meta_keys() {
</ins><span class="cx" style="display: block; padding: 0 10px">                 /* @var WordCamp_Admin $wordcamp_admin */
</span><span class="cx" style="display: block; padding: 0 10px">                global $wordcamp_admin;
</span><span class="cx" style="display: block; padding: 0 10px">                $meta_keys = array_merge( array_keys( $wordcamp_admin->meta_keys( 'all' ) ), [
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -419,7 +302,39 @@
</span><span class="cx" style="display: block; padding: 0 10px">                return $meta_keys;
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+        public function fill_data_row( $event ) {
+               $row = parent::fill_data_row( $event );
+
+               if ( $this->include_counts ) {
+                       $row = array_merge( $row, $this->get_counts( $event ) );
+               }
+
+               return $row;
+       }
+
</ins><span class="cx" style="display: block; padding: 0 10px">         /**
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         * Create an object of this class with relevant requirements passed to constructor
+        *
+        * @param string $context Can be 'public' or 'private'
+        *
+        * @return Base_Details
+        */
+       static public function create_shadow_report_obj( $context ) {
+               return new self( null, null, false, ['public' => 'public' === $context ] );
+       }
+
+       /**
+        * Render list of fields that can be present in exported CSV.
+        *
+        * @param string $context
+        * @param array $field_defaults
+        */
+       static public function render_available_fields( $context = 'public', array $field_defaults = [] ) {
+               $shadow_report = self::create_shadow_report_obj( $context );
+               self::render_available_fields_in_report( $shadow_report, $context, $field_defaults );
+       }
+
+       /**
</ins><span class="cx" style="display: block; padding: 0 10px">          * Count the number of various post types for a WordCamp.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * If the WordCamp doesn't have a site yet, the counts will all be zero.
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -428,7 +343,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @return array
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        protected function get_counts( WP_Post $wordcamp ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ public function get_counts( WP_Post $wordcamp ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                 $counts = [
</span><span class="cx" style="display: block; padding: 0 10px">                        'Tickets'    => 0,
</span><span class="cx" style="display: block; padding: 0 10px">                        'Speakers'   => 0,
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -475,77 +390,6 @@
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         * Render HTML form inputs for the fields that are available for inclusion in the spreadsheet.
-        *
-        * @param string $context        'public' or 'private'. Default 'public'.
-        * @param array  $field_defaults Optional. An associative array where the keys are field keys and the values
-        *                               are extra attributes for those field inputs. Examples: checked or required.
-        */
-       public static function render_available_fields( $context = 'public', array $field_defaults = [] ) {
-               $field_order      = array_fill_keys( self::get_field_order(), '' );
-               $field_defaults   = array_replace( $field_order, $field_defaults );
-
-               $shadow_report = new self( null, [], false, [ 'public' => ( 'private' === $context ) ? false : true ] );
-
-               $available_fields = array_intersect_key( $field_defaults, $shadow_report->get_data_fields_safelist() );
-               ?>
-               <fieldset class="fields-container">
-                       <legend class="fields-label">Available Fields</legend>
-
-                       <?php foreach ( $available_fields as $field_name => $extra_props ) : ?>
-                               <div class="field-checkbox">
-                                       <input
-                                               type="checkbox"
-                                               id="fields-<?php echo esc_attr( $field_name ); ?>"
-                                               name="fields[]"
-                                               value="<?php echo esc_attr( $field_name ); ?>"
-                                               <?php if ( $extra_props && is_string( $extra_props ) ) echo esc_html( $extra_props ); ?>
-                                       />
-                                       <label for="fields-<?php echo esc_attr( $field_name ); ?>">
-                                               <?php echo esc_attr( $field_name ); ?>
-                                       </label>
-                               </div>
-                       <?php endforeach; ?>
-               </fieldset>
-               <?php
-       }
-
-       /**
-        * Register all assets used by this report.
-        *
-        * @return void
-        */
-       public static function register_assets() {
-               wp_register_script(
-                       self::$slug,
-                       get_assets_url() . 'js/' . self::$slug . '.js',
-                       array(),
-                       filemtime( get_assets_dir_path() . 'js/' . self::$slug . '.js' ),
-                       true
-               );
-
-               wp_register_style(
-                       self::$slug,
-                       get_assets_url() . 'css/' . self::$slug . '.css',
-                       array(),
-                       filemtime( get_assets_dir_path() . 'css/' . self::$slug . '.css' ),
-                       'screen'
-               );
-       }
-
-       /**
-        * Enqueue JS and CSS assets for this report's admin interface.
-        *
-        * @return void
-        */
-       public static function enqueue_admin_assets() {
-               self::register_assets();
-
-               wp_enqueue_script( self::$slug );
-               wp_enqueue_style( self::$slug );
-       }
-
-       /**
</del><span class="cx" style="display: block; padding: 0 10px">          * Render the page for this report in the WP Admin.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @return void
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -581,68 +425,49 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        return;
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                if ( wp_verify_nonce( $nonce, 'run-report' ) && current_user_can( 'manage_network' ) ) {
-                       $error = null;
-                       $range = null;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         if ( ! wp_verify_nonce( $nonce, 'run-report' ) ) {
+                       return;
+               }
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        if ( $start_date || $end_date ) {
-                               try {
-                                       $range = validate_date_range( $start_date, $end_date, [
-                                               'allow_future_start' => true,
-                                               'earliest_start'     => new DateTime( '2006-01-01' ), // No WordCamp posts before 2006.,
-                                       ] );
-                               } catch ( Exception $e ) {
-                                       $error = new WP_Error(
-                                               self::$slug . '-date-range-error',
-                                               $e->getMessage()
-                                       );
-                               }
-                       }
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         if ( ! current_user_can( 'manage_network' ) ) {
+                       return;
+               }
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        $include_counts = false;
-                       if ( ! empty( array_intersect( $fields, [ 'Tickets', 'Speakers', 'Sponsors', 'Organizers' ] ) ) ) {
-                               $include_counts = true;
-                       }
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $error = null;
+               $range = null;
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        // The "Name" field should always be included, but does not get submitted because the input is disabled,
-                       // so add it in here.
-                       $fields[] = 'Name';
-
-                       $options = array(
-                               'fields' => $fields,
-                               'public' => false,
-                       );
-
-                       $report = new self( $range, [], $include_counts, $options );
-
-                       $filename = [ $report::$name ];
-                       if ( $report->range instanceof Date_Range ) {
-                               $filename[] = $report->range->start->format( 'Y-m-d' );
-                               $filename[] = $report->range->end->format( 'Y-m-d' );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         if ( $start_date || $end_date ) {
+                       try {
+                               $range = validate_date_range( $start_date, $end_date, [
+                                       'allow_future_start' => true,
+                                       'earliest_start'     => new DateTime( '2006-01-01' ), // No WordCamp posts before 2006.,
+                               ] );
+                       } catch ( Exception $e ) {
+                               $error = new WP_Error(
+                                       self::$slug . '-date-range-error',
+                                       $e->getMessage()
+                               );
</ins><span class="cx" style="display: block; padding: 0 10px">                         }
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        if ( $report->include_counts ) {
-                               $filename[] = 'include-counts';
-                       }
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         }
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        $data = $report->prepare_data_for_display( $report->get_data() );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $include_counts = false;
+               if ( ! empty( array_intersect( $fields, [ 'Tickets', 'Speakers', 'Sponsors', 'Organizers' ] ) ) ) {
+                       $include_counts = true;
+               }
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        $headers = ( ! empty( $data ) ) ? array_keys( $data[0] ) : [];
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         // The "Name" field should always be included, but does not get submitted because the input is disabled,
+               // so add it in here.
+               $fields[] = 'Name';
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        $exporter = new Export_CSV( array(
-                               'filename' => $filename,
-                               'headers'  => $headers,
-                               'data'     => $data,
-                       ) );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $options = array(
+                       'fields' => $fields,
+                       'public' => false,
+               );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        if ( ! empty( $report->error->get_error_messages() ) ) {
-                               $exporter->error = $report->merge_errors( $report->error, $exporter->error );
-                       }
</del><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        if ( $error instanceof WP_Error ) {
-                               $exporter->error = $report->merge_errors( $error, $exporter->error );
-                       }
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $report = new self( $range, null, $include_counts, $options );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        $exporter->emit_file();
-               } // End if().
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         self::export_to_file_common( $report );
</ins><span class="cx" style="display: block; padding: 0 10px">         }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span></span></pre></div>
<a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampreportsclassesreportclasswordcampstatusphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/classes/report/class-wordcamp-status.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-reports/classes/report/class-wordcamp-status.php 2018-10-16 17:09:51 UTC (rev 7733)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/classes/report/class-wordcamp-status.php   2018-10-16 17:30:06 UTC (rev 7734)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -9,10 +9,10 @@
</span><span class="cx" style="display: block; padding: 0 10px"> use Exception;
</span><span class="cx" style="display: block; padding: 0 10px"> use DateTime;
</span><span class="cx" style="display: block; padding: 0 10px"> use WP_Error, WP_Post;
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-use function WordCamp\Reports\{get_assets_url, get_assets_dir_path, get_views_dir_path};
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+use function WordCamp\Reports\{get_views_dir_path};
</ins><span class="cx" style="display: block; padding: 0 10px"> use WordCamp\Reports\Utility\Date_Range;
</span><span class="cx" style="display: block; padding: 0 10px"> use function WordCamp\Reports\Validation\{validate_date_range, validate_wordcamp_status};
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-use function WordCamp\Reports\Time\{year_array, quarter_array, month_array, convert_time_period_to_date_range};
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+use function WordCamp\Reports\Time\{year_array, quarter_array, month_array};
</ins><span class="cx" style="display: block; padding: 0 10px"> use WordCamp_Loader;
</span><span class="cx" style="display: block; padding: 0 10px"> use WordCamp\Utilities\Export_CSV;
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -23,7 +23,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">  *
</span><span class="cx" style="display: block; padding: 0 10px">  * @package WordCamp\Reports\Report
</span><span class="cx" style="display: block; padding: 0 10px">  */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-class WordCamp_Status extends Base {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+class WordCamp_Status extends Base_Status {
</ins><span class="cx" style="display: block; padding: 0 10px">         /**
</span><span class="cx" style="display: block; padding: 0 10px">         * Report name.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -222,7 +222,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                        $latest_log    = end( $logs );
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        $latest_status = $this->get_log_status_result( $latest_log );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 $latest_status = $this->get_log_status_result( $latest_log, WordCamp_Loader::get_post_statuses() );
</ins><span class="cx" style="display: block; padding: 0 10px">                         reset( $logs );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                        // Trim log entries occurring before the date range.
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -348,68 +348,11 @@
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @return array
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        protected function get_wordcamp_status_logs( WP_Post $wordcamp ) {
-               $log_entries = get_post_meta( $wordcamp->ID, '_status_change' );
-
-               if ( ! empty( $log_entries ) ) {
-                       // Sort log entries in chronological order.
-                       usort( $log_entries, function( $a, $b ) {
-                               if ( $a['timestamp'] === $b['timestamp'] ) {
-                                       return 0;
-                               }
-
-                               return ( $a['timestamp'] > $b['timestamp'] ) ? 1 : -1;
-                       } );
-
-                       return $log_entries;
-               }
-
-               return array();
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ protected function get_wordcamp_status_logs( \WP_Post $wordcamp ) {
+               return $this->sort_logs( get_post_meta( $wordcamp->ID, '_status_change' ) );
</ins><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         * Determine the ending status of a particular status change event.
-        *
-        * E.g. for this event:
-        *
-        *     Needs Vetting → More Info Requested
-        *
-        * The ending status would be "More Info Requested".
-        *
-        * @param array $log_entry A status change log entry.
-        *
-        * @return string
-        */
-       protected function get_log_status_result( $log_entry ) {
-               if ( isset( $log_entry['message'] ) ) {
-                       $pieces = explode( ' &rarr; ', $log_entry['message'] );
-
-                       if ( isset( $pieces[1] ) ) {
-                               return $this->get_status_id_from_name( $pieces[1] );
-                       }
-               }
-
-               return '';
-       }
-
-       /**
-        * Given the ID of a WordCamp status, determine the ID string.
-        *
-        * @param string $status_name A WordCamp status name.
-        *
-        * @return string
-        */
-       protected function get_status_id_from_name( $status_name ) {
-               $statuses = array_flip( WordCamp_Loader::get_post_statuses() );
-
-               if ( isset( $statuses[ $status_name ] ) ) {
-                       return $statuses[ $status_name ];
-               }
-
-               return '';
-       }
-
-       /**
</del><span class="cx" style="display: block; padding: 0 10px">          * A list of status IDs for statuses that indicate a camp is not active.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @return array
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -446,40 +389,51 @@
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         * Register all assets used by this report.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+  * Determine whether to render the public report form.
</ins><span class="cx" style="display: block; padding: 0 10px">          *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         * @return void
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+  * This shortcode is limited to use on pages.
+        *
+        * @return string HTML content to display shortcode.
</ins><span class="cx" style="display: block; padding: 0 10px">          */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        protected static function register_assets() {
-               wp_register_script(
-                       self::$slug,
-                       get_assets_url() . 'js/' . self::$slug . '.js',
-                       array( 'jquery', 'select2' ),
-                       filemtime( get_assets_dir_path() . 'js/' . self::$slug . '.js' ),
-                       true
-               );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ public static function handle_shortcode() {
+               $html = '';
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                wp_register_style(
-                       self::$slug,
-                       get_assets_url() . 'css/' . self::$slug . '.css',
-                       array( 'select2' ),
-                       filemtime( get_assets_dir_path() . 'css/' . self::$slug . '.css' ),
-                       'screen'
-               );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         if ( 'page' === get_post_type() ) {
+                       self::register_assets();
+
+                       wp_enqueue_style( 'select2' );
+                       wp_enqueue_script( self::$slug );
+
+                       ob_start();
+                       self::render_public_page();
+                       $html = ob_get_clean();
+               }
+
+               return $html;
</ins><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         * Enqueue JS and CSS assets for this report's admin interface.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+  * Render the page for this report on the front end.
</ins><span class="cx" style="display: block; padding: 0 10px">          *
</span><span class="cx" style="display: block; padding: 0 10px">         * @return void
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        public static function enqueue_admin_assets() {
-               self::register_assets();
-               WordCamp_Details::register_assets();
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ public static function render_public_page() {
+               $params = self::parse_public_report_input();
+               $years    = year_array( absint( date( 'Y' ) ), 2015 );
+               $quarters = quarter_array();
+               $months   = month_array();
+               $statuses = WordCamp_Loader::get_post_statuses();
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                wp_enqueue_style( WordCamp_Details::$slug );
-               wp_enqueue_script( self::$slug );
-               wp_enqueue_style( self::$slug );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $error = $params['error'];
+               $report = null;
+               $period = $params['period'];
+               $year = $params['year'];
+               $status = $params['status'];
+               if ( ! empty( $params ) && isset( $params['range'] ) ) {
+                       $report = new self( $params['range']->start, $params['range']->end, $params['status'], $params['options'] );
+               }
+
+               include get_views_dir_path() . 'public/wordcamp-status.php';
</ins><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -618,74 +572,8 @@
</span><span class="cx" style="display: block; padding: 0 10px">                } // End if().
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        /**
-        * Determine whether to render the public report form.
-        *
-        * This shortcode is limited to use on pages.
-        *
-        * @return string HTML content to display shortcode.
-        */
-       public static function handle_shortcode() {
-               $html = '';
-
-               if ( 'page' === get_post_type() ) {
-                       self::register_assets();
-
-                       wp_enqueue_style( 'select2' );
-                       wp_enqueue_script( self::$slug );
-
-                       ob_start();
-                       self::render_public_page();
-                       $html = ob_get_clean();
-               }
-
-               return $html;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ static public function get_report_object( $date_range, $status, $options ) {
+               return new self( $date_range->start, $date_range->end, $status, $options );
</ins><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        /**
-        * Render the page for this report on the front end.
-        *
-        * @return void
-        */
-       public static function render_public_page() {
-               // Apparently 'year' is a reserved URL parameter on the front end, so we prepend 'report-'.
-               $year   = filter_input( INPUT_GET, 'report-year', FILTER_VALIDATE_INT );
-               $period = filter_input( INPUT_GET, 'period' );
-               $status = filter_input( INPUT_GET, 'status' );
-               $action = filter_input( INPUT_GET, 'action' );
-
-               $years    = year_array( absint( date( 'Y' ) ), 2015 );
-               $quarters = quarter_array();
-               $months   = month_array();
-               $statuses = WordCamp_Loader::get_post_statuses();
-
-               if ( ! $year ) {
-                       $year = absint( date( 'Y' ) );
-               }
-
-               if ( ! $period ) {
-                       $period = absint( date( 'm' ) );
-               }
-
-               $report = null;
-
-               if ( 'Show results' === $action ) {
-                       try {
-                               $range = convert_time_period_to_date_range( $year, $period );
-                       } catch ( Exception $e ) {
-                               $error = new WP_Error(
-                                       self::$slug . '-time-period-error',
-                                       $e->getMessage()
-                               );
-                       }
-
-                       $options = array(
-                               'earliest_start' => new DateTime( '2015-01-01' ), // No status log data before 2015.
-                       );
-
-                       $report = new self( $range->start, $range->end, $status, $options );
-               }
-
-               include get_views_dir_path() . 'public/wordcamp-status.php';
-       }
</del><span class="cx" style="display: block; padding: 0 10px"> }
</span></span></pre></div>
<a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampreportsindexphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/index.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-reports/index.php        2018-10-16 17:09:51 UTC (rev 7733)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/index.php  2018-10-16 17:30:06 UTC (rev 7734)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -138,6 +138,8 @@
</span><span class="cx" style="display: block; padding: 0 10px">                __NAMESPACE__ . '\Report\Meetup_Groups',
</span><span class="cx" style="display: block; padding: 0 10px">                __NAMESPACE__ . '\Report\Meetup_Events',
</span><span class="cx" style="display: block; padding: 0 10px">                __NAMESPACE__ . '\Report\WordCamp_Payment_Methods',
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                __NAMESPACE__ . '\Report\Meetup_Status',
+               __NAMESPACE__ . '\Report\Meetup_Details',
</ins><span class="cx" style="display: block; padding: 0 10px">         );
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span></span></pre></div>
<a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampreportsviewshtmlmeetupstatusphp"></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-reports/views/html/meetup-status.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-reports/views/html/meetup-status.php                             (rev 0)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/views/html/meetup-status.php       2018-10-16 17:30:06 UTC (rev 7734)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,64 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+       /**
+        * @package WordCamp\Reports
+        */
+
+       namespace WordCamp\Reports\Views\HTML\Meetup_Status;
+       defined( 'WPINC' ) || die();
+
+       use DateTime;
+
+       /** @var DateTime $start_date */
+       /** @var DateTime $end_date */
+       /** @var string $status */
+       /** @var array $meetups */
+       /** @var array $statuses */
+?>
+
+<?php if ( count( $meetups ) ) : ?>
+       <h3 id="active-heading">
+               <?php if ( $status && $status !== 'any' ) : ?>
+                       Meetups set to &ldquo;<?php echo esc_html( $statuses[ $status ] ); ?>&rdquo;
+               <?php else : ?>
+                       Meetups activity
+               <?php endif; ?>
+               <?php if ( $start_date->format( 'Y-m-d' ) === $end_date->format( 'Y-m-d' ) ) : ?>
+                       on <?php echo esc_html( $start_date->format( 'M jS, Y' ) ); ?>
+               <?php else : ?>
+                       between <?php echo esc_html( $start_date->format( 'M jS, Y' ) ); ?> and <?php echo esc_html( $end_date->format( 'M jS, Y' ) ); ?>
+               <?php endif; ?>
+       </h3>
+       <table class="striped widefat but-not-too-wide">
+               <tr>
+                       <td>Meetups</td>
+                       <td class="number"><?php echo number_format_i18n( count( $meetups ) ); ?></td>
+               </tr>
+       </table>
+
+       <?php foreach ( $meetups as $meetup ) : ?>
+               <p><strong class="active-camp"><?php echo esc_html( $meetup['name'] ); ?></strong> &ndash; <?php echo esc_html( $statuses[ $meetup['latest_status'] ] ); ?></p>
+               <ul class="status-log ul-disc">
+                       <?php foreach ( $meetup['logs'] as $log ) : ?>
+                               <li><?php
+                                               echo date( 'Y-m-d', $log['timestamp'] );
+                                               echo ': ';
+                                               echo esc_html( $log['message'] );
+                                       ?></li>
+                       <?php endforeach; ?>
+               </ul>
+       <?php endforeach; ?>
+<?php endif; ?>
+
+<?php if ( empty( $meetups ) ) : ?>
+       <p>
+               No data
+               <?php if ( $status && $status !== 'any' ) : ?>
+                       involving &ldquo;<?php echo esc_html( $statuses[ $status ] ); ?>&rdquo;
+               <?php endif; ?>
+               <?php if ( $start_date->format( 'Y-m-d' ) === $end_date->format( 'Y-m-d' ) ) : ?>
+                       on <?php echo esc_html( $start_date->format( 'M jS, Y' ) ); ?>
+               <?php else : ?>
+                       between <?php echo esc_html( $start_date->format( 'M jS, Y' ) ); ?> and <?php echo esc_html( $end_date->format( 'M jS, Y' ) ); ?>
+               <?php endif; ?>
+       </p>
+<?php endif; ?>
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/views/html/meetup-status.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampreportsviewspublicmeetupstatusphp"></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-reports/views/public/meetup-status.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-reports/views/public/meetup-status.php                           (rev 0)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/views/public/meetup-status.php     2018-10-16 17:30:06 UTC (rev 7734)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,69 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+       /**
+        * @package WordCamp\Reports
+        */
+
+       namespace WordCamp\Reports\Views\Report\WordCamp_Status;
+       defined( 'WPINC' ) || die();
+
+       use WordCamp\Reports\Report;
+
+       /** @var string $year */
+       /** @var string $period */
+       /** @var string $status */
+       /** @var array  $years */
+       /** @var array  $quarters */
+       /** @var array  $months */
+       /** @var array  $statuses */
+       /** @var Report\Meetup_Status|null $report */
+?>
+
+<div id="<?php echo esc_attr( Report\Meetup_Status::$slug ); ?>-report" class="report-container">
+       <p class="report-description">
+               <?php echo wp_kses_post( Report\Meetup_Status::$description ); ?>
+       </p>
+
+       <form method="get" action="" class="report-form">
+               <div class="field_report-year">
+                       <label for="report-year">Year</label>
+                       <select id="report-year" name="report-year">
+                               <?php foreach ( $years as $year_value ) : ?>
+                                       <option value="<?php echo esc_attr( $year_value ); ?>"<?php selected( $year_value, $year ); ?>><?php echo esc_html( $year_value ); ?></option>
+                               <?php endforeach; ?>
+                       </select>
+               </div>
+
+               <div class="field_period">
+                       <label for="period">Time Period</label>
+                       <select id="period" name="period">
+                               <option value="all"<?php selected( 'all' === $period ); ?>>Entire year</option>
+                               <?php foreach ( $quarters as $quarter_value => $quarter_label ) : ?>
+                                       <option value="<?php echo esc_attr( $quarter_value ); ?>"<?php selected( $quarter_value, $period ); ?>><?php echo esc_html( $quarter_label ); ?></option>
+                               <?php endforeach; ?>
+                               <?php foreach ( $months as $month_value => $month_label ) : ?>
+                                       <option value="<?php echo esc_attr( $month_value ); ?>"<?php selected( $month_value, $period ); ?>><?php echo esc_html( $month_label ); ?></option>
+                               <?php endforeach; ?>
+                       </select>
+               </div>
+
+               <div class="field_status">
+                       <label for="status">Status</label>
+                       <select id="status" name="status">
+                               <option value="any"<?php selected( ( ! $status || 'any' === $status ) ); ?>>Any</option>
+                               <?php foreach ( $statuses as $value => $label ) : ?>
+                                       <option value="<?php echo esc_attr( $value ); ?>"<?php selected( $value, $status ); ?>><?php echo esc_html( $label ); ?></option>
+                               <?php endforeach; ?>
+                       </select>
+               </div>
+
+               <div class="submit_show-results">
+                       <?php submit_button( 'Show results', 'primary', 'action', false ); ?>
+               </div>
+       </form>
+
+       <?php if ( $report instanceof Report\Meetup_Status ) : ?>
+               <div class="report-results">
+                       <?php $report->render_html(); ?>
+               </div>
+       <?php endif; ?>
+</div>
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/views/public/meetup-status.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampreportsviewsreportmeetupdetailsphp"></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-reports/views/report/meetup-details.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-reports/views/report/meetup-details.php                          (rev 0)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/views/report/meetup-details.php    2018-10-16 17:30:06 UTC (rev 7734)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,37 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/**
+ * @package WordCamp\Reports
+ */
+
+namespace WordCamp\Reports\Views\Report\Meetup_Details;
+defined( 'WPINC' ) || die();
+
+use WordCamp\Reports;
+use WordCamp\Reports\Report;
+
+/** @var array $field_defaults */
+?>
+
+<div class="wrap">
+       <h1>
+               <a href="<?php echo esc_attr( Reports\get_page_url() ); ?>">WordCamp Reports</a>
+               &raquo;
+               <?php echo esc_html( Report\Meetup_Details::$name ); ?>
+       </h1>
+
+       <?php echo wpautop( wp_kses_post( Report\Meetup_Details::$description ) ); ?>
+
+       <h4>Methodology</h4>
+
+       <?php echo wpautop( wp_kses_post( Report\Meetup_Details::$methodology ) ); ?>
+
+       <form method="post" action="">
+               <input type="hidden" name="action" value="run-report" />
+               <?php wp_nonce_field( 'run-report', Report\Meetup_Details::$slug . '-nonce' ); ?>
+
+
+               <?php Report\Meetup_Details::render_available_fields( 'private', $field_defaults ) ?>
+
+               <?php submit_button( 'Export CSV', 'primary', 'action', false ); ?>
+       </form>
+</div>
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/views/report/meetup-details.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampreportsviewsreportmeetupstatusphp"></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-reports/views/report/meetup-status.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-reports/views/report/meetup-status.php                           (rev 0)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/views/report/meetup-status.php     2018-10-16 17:30:06 UTC (rev 7734)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,83 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+       /**
+        * @package WordCamp\Reports
+        */
+
+       namespace WordCamp\Reports\Views\Report\Meetup_Status;
+       defined( 'WPINC' ) || die();
+
+       use WordCamp\Reports;
+       use WordCamp\Reports\Report;
+
+       /** @var string $start_date */
+       /** @var string $end_date */
+       /** @var string $status */
+       /** @var array  $statuses */
+       /** @var array  $field_defaults */
+       /** @var Report\WordCamp_Status|null $report */
+?>
+
+<div class="wrap">
+       <h1>
+               <a href="<?php echo esc_attr( Reports\get_page_url() ); ?>">WordCamp Reports</a>
+               &raquo;
+               <?php echo esc_html( Report\Meetup_Status::$name ); ?>
+       </h1>
+
+       <?php echo wpautop( wp_kses_post( Report\Meetup_Status::$description ) ); ?>
+
+       <h4>Methodology</h4>
+
+       <?php echo wpautop( wp_kses_post( Report\Meetup_Status::$methodology ) ); ?>
+
+       <form method="post" action="">
+               <input type="hidden" name="action" value="run-report" />
+               <?php wp_nonce_field( 'run-report', Report\Meetup_Status::$slug . '-nonce' ); ?>
+
+               <table class="form-table">
+                       <tbody>
+                       <tr>
+                               <th scope="row"><label for="start-date">Start Date</label></th>
+                               <td><input type="date" id="start-date" name="start-date" value="<?php echo esc_attr( $start_date ) ?>" required /></td>
+                       </tr>
+                       <tr>
+                               <th scope="row"><label for="end-date">End Date</label></th>
+                               <td><input type="date" id="end-date" name="end-date" value="<?php echo esc_attr( $end_date ) ?>" required /></td>
+                       </tr>
+                       <tr>
+                               <th scope="row"><label for="status">Status (optional)</label></th>
+                               <td>
+                                       <select id="status" name="status" class="select2-container">
+                                               <option value="any"<?php selected( ( ! $status || 'any' === $status ) ); ?>>Any</option>
+                                               <?php foreach ( $statuses as $value => $label ) : ?>
+                                                       <option value="<?php echo esc_attr( $value ); ?>"<?php selected( $value, $status ); ?>><?php echo esc_attr( $label ); ?></option>
+                                               <?php endforeach; ?>
+                                       </select>
+                                       <p class="description">
+                                               This will select all Meetups that had this status at any time during the date range.
+                                       </p>
+                               </td>
+                       </tr>
+                       <tr>
+                               <th scope="row"><label for="refresh">Refresh results</label></th>
+                               <td><input type="checkbox" id="refresh" name="refresh" /></td>
+                       </tr>
+                       </tbody>
+               </table>
+
+               <?php submit_button( 'Show results', 'primary', 'action', false ); ?>
+
+               <button id="fields-toggle" class="secondary button-secondary">Export resulting Meetups as a CSV</button>
+
+               <section id="fields-section" class="hidden">
+                       <?php Report\Meetup_Details::render_available_fields( 'private', $field_defaults ); ?>
+                       <?php submit_button( 'Export CSV', 'primary', 'action', false ); ?>
+               </section>
+       </form>
+
+       <?php if ( $report instanceof Report\Meetup_Status ) : ?>
+               <div class="report-results">
+                       <?php $report->render_html(); ?>
+               </div>
+       <?php endif; ?>
+</div>
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/views/report/meetup-status.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span></div>

</body>
</html>