<!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>[7516] sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports: WordCamp Reports: Add WordCamp Details report</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/7516">7516</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/7516","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>coreymckrill</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2018-07-28 00:46:28 +0000 (Sat, 28 Jul 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 Reports: Add WordCamp Details report

This is the first iteration of a report that allows WPCS staff to download a
spreadsheet of WordCamps and related data.</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_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_htmlwpcontentpluginswordcampreportsclassesreportclasswordcampdetailsphp">sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/classes/report/class-wordcamp-details.php</a></li>
<li>sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/classes/utility/</li>
<li><a href="#sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampreportsclassesutilityclassdaterangephp">sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/classes/utility/class-date-range.php</a></li>
<li>sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/includes/</li>
<li><a href="#sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampreportsincludestimephp">sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/includes/time.php</a></li>
<li><a href="#sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampreportsincludesvalidationphp">sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/includes/validation.php</a></li>
<li><a href="#sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampreportsviewsreportwordcampdetailsphp">sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/views/report/wordcamp-details.php</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<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-07-27 17:02:38 UTC (rev 7515)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/classes/report/class-base.php      2018-07-28 00:46:28 UTC (rev 7516)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -134,6 +134,21 @@
</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><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.
+        */
+       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;
+       }
+
+       /**
</ins><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">@@ -141,12 +156,8 @@
</span><span class="cx" style="display: block; padding: 0 10px">         * @return array
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="cx" style="display: block; padding: 0 10px">        protected function filter_data_fields( array $data ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $safelist = $this->public_data_fields;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $safelist = $this->get_data_fields_safelist();
</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 ( false === $this->options['public'] ) {
-                       $safelist = array_merge( $safelist, $this->private_data_fields );
-               }
-
</del><span class="cx" style="display: block; padding: 0 10px">                 array_walk( $data, function ( &$row ) use ( $safelist ) {
</span><span class="cx" style="display: block; padding: 0 10px">                        $row = shortcode_atts( $safelist, $row );
</span><span class="cx" style="display: block; padding: 0 10px">                } );
</span></span></pre></div>
<a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampreportsclassesreportclasswordcampdetailsphp"></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-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                                (rev 0)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/classes/report/class-wordcamp-details.php  2018-07-28 00:46:28 UTC (rev 7516)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,484 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/**
+ * @package WordCamp\Reports
+ */
+
+namespace WordCamp\Reports\Report;
+defined( 'WPINC' ) || die();
+
+use Exception;
+use DateTime;
+use WP_Post;
+use WordCamp\Reports;
+use WordCamp\Reports\Report\WordCamp_Status;
+use WordCamp\Reports\Utility\Date_Range;
+use function WordCamp\Reports\Validation\{validate_date_range, validate_wordcamp_status};
+use function WordCamp\Reports\Time\modify_cache_expiration_for_date_range;
+use WordCamp_Admin, WordCamp_Loader;
+use WordCamp\Utilities\Export_CSV;
+
+/**
+ * Class WordCamp_Details
+ *
+ * A report class for exporting a spreadsheet of WordCamps.
+ *
+ * @package WordCamp\Reports\Report
+ */
+class WordCamp_Details extends Base {
+       /**
+        * Report name.
+        *
+        * @var string
+        */
+       public static $name = 'WordCamp Details';
+
+       /**
+        * Report slug.
+        *
+        * @var string
+        */
+       public static $slug = 'wordcamp-details';
+
+       /**
+        * Report description.
+        *
+        * @var string
+        */
+       public static $description = 'Details about WordCamps occurring within a specified date range.';
+
+       /**
+        * Report methodology.
+        *
+        * @var string
+        */
+       public static $methodology = "
+               <ol>
+                       <li>Retrieve WordCamp posts that fit within the date range and other optional criteria.</li>
+                       <li>Extract the post meta values for each post that match the fields requested.</li>
+                       <li>Walk all of the extracted data and format it for display.</li>
+               </ol>
+       ";
+
+       /**
+        * Report group.
+        *
+        * @var string
+        */
+       public static $group = 'wordcamp';
+
+       /**
+        * 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 = '';
+
+       /**
+        * The fields to include in the report output.
+        *
+        * @var array
+        */
+       public $fields = [];
+
+       /**
+        * 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 = [];
+
+       /**
+        * WordCamp_Details constructor.
+        *
+        * @param string $start_date The start of the date range for the report.
+        * @param string $end_date   The end of the date range for the report.
+        * @param string $status     Optional. The status ID to filter for in the report.
+        * @param array  $fields     Not implemented yet.
+        * @param array  $options    {
+        *     Optional. Additional report parameters.
+        *     See Base::__construct and the functions in WordCamp\Reports\Validation for additional parameters.
+        *
+        *     @type bool $include_dateless True to include WordCamps that don't have a date set. Default false.
+        * }
+        */
+       public function __construct( $start_date, $end_date, $status = '', array $fields = [], array $options = [] ) {
+               // Report-specific options.
+               $options = wp_parse_args( $options, array(
+                       'include_dateless' => false,
+               ) );
+
+               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()
+                       );
+               }
+
+               if ( $status && 'any' !== $status ) {
+                       try {
+                               $this->status = validate_wordcamp_status( $status, $options );
+                       } catch ( Exception $e ) {
+                               $this->error->add(
+                                       self::$slug . '-status-error',
+                                       $e->getMessage()
+                               );
+                       }
+               }
+
+               try {
+                       $this->fields = $this->validate_fields_input( $fields );
+               } catch ( Exception $e ) {
+                       $this->error->add(
+                               self::$slug . '-fields-error',
+                               $e->getMessage()
+                       );
+               }
+
+               $this->public_data_fields = array_fill_keys( array_merge(
+                       [
+                               'ID',
+                               'Name',
+                               'Status',
+                       ],
+                       WordCamp_Loader::get_public_meta_keys()
+               ), '' );
+
+               $this->private_data_fields = array_fill_keys( array_diff(
+                       $this->get_meta_keys(),
+                       array_keys( $this->public_data_fields )
+               ), '' );
+       }
+
+       /**
+        * TODO
+        *
+        * @param array $fields
+        */
+       protected function validate_fields_input( array $fields ) {}
+
+       /**
+        * Generate a cache key.
+        *
+        * @return string
+        */
+       protected function get_cache_key() {
+               $cache_key = parent::get_cache_key() . '_' . $this->range->start->getTimestamp() . '-' . $this->range->end->getTimestamp();
+
+               if ( $this->status ) {
+                       $cache_key .= '_' . $this->status;
+               }
+
+               return $cache_key;
+       }
+
+       /**
+        * Generate a cache expiration interval.
+        *
+        * @return int A time interval in seconds.
+        */
+       protected function get_cache_expiration() {
+               $original_expiration = parent::get_cache_expiration();
+
+               try {
+                       $expiration = modify_cache_expiration_for_date_range(
+                               $original_expiration,
+                               $this->range
+                       );
+               } catch ( Exception $e ) {
+                       $this->error->add(
+                               self::$slug . '-cache-error',
+                               $e->getMessage()
+                       );
+
+                       return $original_expiration;
+               }
+
+               return $expiration;
+       }
+
+       /**
+        * 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();
+               }
+
+               // Maybe use cached data.
+               $data = $this->maybe_get_cached_data();
+               if ( is_array( $data ) ) {
+                       return $data;
+               }
+
+               $data = [];
+
+               $wordcamp_posts = $this->get_wordcamp_posts();
+
+               foreach ( $wordcamp_posts as $post ) {
+                       $data[] = $this->extract_wordcamp_fields( $post );
+               }
+
+               $data = $this->filter_data_fields( $data );
+               $this->maybe_cache_data( $data );
+
+               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;
+       }
+
+       /**
+        * Format the data for human-readable display.
+        *
+        * @param array $data The data to prepare.
+        *
+        * @return array
+        */
+       protected function prepare_data_for_display( array $data ) {
+               $all_statuses = WordCamp_Loader::get_post_statuses();
+
+               array_walk( $data, function( &$row ) use ( $all_statuses ) {
+                       foreach ( $row as $key => $value ) {
+                               switch ( $key ) {
+                                       case 'Status':
+                                               $row[ $key ] = $all_statuses[ $value ];
+                                               break;
+                                       case 'Start Date (YYYY-mm-dd)':
+                                       case 'End Date (YYYY-mm-dd)':
+                                       case 'Contributor Day Date (YYYY-mm-dd)':
+                                               $row[ $key ] = ( $value ) ? date( 'Y-m-d', $value ) : '';
+                                               break;
+                                       case 'Exhibition Space Available':
+                                       case 'Contributor Day':
+                                               $row[ $key ] = ( $value ) ? 'Yes' : 'No';
+                                               break;
+                                       case '_venue_coordinates':
+                                               if ( is_array( $value ) ) {
+                                                       $row[ $key ] = implode( ', ', $value );
+                                               }
+                                               break;
+                               }
+                       }
+               } );
+
+               return $data;
+       }
+
+       /**
+        * Get all current WordCamp posts.
+        *
+        * @return array
+        */
+       protected function get_wordcamp_posts() {
+               $post_args = array(
+                       'post_type'           => WCPT_POST_TYPE_ID,
+                       'post_status'         => 'any',
+                       'posts_per_page'      => 9999,
+                       'nopaging'            => true,
+                       'no_found_rows'       => false,
+                       'ignore_sticky_posts' => true,
+                       'orderby'             => 'meta_value_num title',
+                       'order'               => 'ASC',
+                       'meta_query'          => [
+                               [
+                                       'key'      => 'Start Date (YYYY-mm-dd)',
+                                       'value'    => array( $this->range->start->getTimestamp(), $this->range->end->getTimestamp() ),
+                                       'compare'  => 'BETWEEN',
+                                       'type'     => 'NUMERIC',
+                               ],
+                       ],
+               );
+
+               if ( $this->options['include_dateless'] ) {
+                       $post_args['meta_query'] = array_merge( $post_args['meta_query'], [
+                               'relation' => 'OR',
+                               [
+                                       'key'     => 'Start Date (YYYY-mm-dd)',
+                                       'compare' => 'NOT EXISTS',
+                               ],
+                               [
+                                       'key'     => 'Start Date (YYYY-mm-dd)',
+                                       'compare' => '=',
+                                       'value'   => '',
+                               ],
+                       ] );
+               }
+
+               if ( $this->options['public'] ) {
+                       $post_args['post_status'] = WordCamp_Loader::get_public_post_statuses();
+               }
+
+               if ( $this->status ) {
+                       $status_report = new WordCamp_Status(
+                               $this->range->start->format( 'Y-m-d' ),
+                               $this->range->end->format( 'Y-m-d' ),
+                               $this->status,
+                               $this->options
+                       );
+
+                       $post_ids = array_keys( $status_report->get_data() );
+
+                       if ( empty( $post_ids ) ) {
+                               return [];
+                       }
+
+                       $post_args['post__in'] = $post_ids;
+               }
+
+               return get_posts( $post_args );
+       }
+
+       /**
+        * Get the values of all the relevant post meta keys for a WordCamp post.
+        *
+        * @param WP_Post $wordcamp
+        *
+        * @return array
+        */
+       protected function extract_wordcamp_fields( WP_Post $wordcamp ) {
+               $meta_keys = $this->get_meta_keys();
+
+               $row = [
+                       'ID'     => $wordcamp->ID,
+                       'Name'   => $wordcamp->post_title,
+                       'Status' => $wordcamp->post_status,
+               ];
+
+               foreach ( $meta_keys as $key ) {
+                       $row[ $key ] = get_post_meta( $wordcamp->ID, $key, true ) ?: '';
+               }
+
+               return $row;
+       }
+
+       /**
+        * Get a list of all the relevant meta keys for WordCamp posts.
+        *
+        * @return array
+        */
+       protected function get_meta_keys() {
+               /* @var WordCamp_Admin $wordcamp_admin */
+               global $wordcamp_admin;
+               $meta_keys = array_merge( array_keys( $wordcamp_admin->meta_keys( 'all' ) ), [
+                       '_venue_coordinates',
+                       '_venue_city',
+                       '_venue_state',
+                       '_venue_country_code',
+                       '_venue_country_name',
+                       '_venue_zip',
+               ] );
+
+               return $meta_keys;
+       }
+
+       /**
+        * 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' );
+               $include_dateless = filter_input( INPUT_POST, 'include_dateless', FILTER_VALIDATE_BOOLEAN );
+               $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' );
+               $statuses         = WordCamp_Loader::get_post_statuses();
+
+               include Reports\get_views_dir_path() . 'report/wordcamp-details.php';
+       }
+
+       /**
+        * Export the report data to a file.
+        *
+        * @return void
+        */
+       public static function export_to_file() {
+               $start_date       = filter_input( INPUT_POST, 'start-date' );
+               $end_date         = filter_input( INPUT_POST, 'end-date' );
+               $include_dateless = filter_input( INPUT_POST, 'include_dateless', FILTER_VALIDATE_BOOLEAN );
+               $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' );
+
+               $report = null;
+
+               if ( 'Export CSV' !== $action ) {
+                       return;
+               }
+
+               if ( wp_verify_nonce( $nonce, 'run-report' ) && current_user_can( 'manage_network' ) ) {
+                       $options = array(
+                               'public'           => false,
+                               'include_dateless' => $include_dateless,
+                               'earliest_start'   => new DateTime( '2006-01-01' ), // No WordCamp posts before 2006.
+                       );
+
+                       if ( $status ) {
+                               $options['earliest_start'] = new DateTime( '2015-01-01' ); // No status log data before 2015.
+                       }
+
+                       if ( $refresh ) {
+                               $options['flush_cache'] = true;
+                       }
+
+                       $report = new self( $start_date, $end_date, $status, [], $options );
+
+                       $filename = [ $report::$name ];
+                       $filename[] = $report->range->start->format( 'Y-m-d' );
+                       $filename[] = $report->range->end->format( 'Y-m-d' );
+                       if ( $report->status ) {
+                               $filename[] = $report->status;
+                       }
+
+                       $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();
+               } // End if().
+       }
+}
</ins></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-07-27 17:02:38 UTC (rev 7515)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/classes/report/class-wordcamp-status.php   2018-07-28 00:46:28 UTC (rev 7516)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -6,8 +6,10 @@
</span><span class="cx" style="display: block; padding: 0 10px"> namespace WordCamp\Reports\Report;
</span><span class="cx" style="display: block; padding: 0 10px"> defined( 'WPINC' ) || die();
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+use Exception;
+use WordCamp\Reports;
+use function WordCamp\Reports\Validation\validate_wordcamp_status;
</ins><span class="cx" style="display: block; padding: 0 10px"> use WordCamp_Loader;
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-use WordCamp\Reports;
</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_Status
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -100,45 +102,21 @@
</span><span class="cx" style="display: block; padding: 0 10px">        public function __construct( $start_date, $end_date, $status = '', array $options = array() ) {
</span><span class="cx" style="display: block; padding: 0 10px">                // Report-specific options.
</span><span class="cx" style="display: block; padding: 0 10px">                $options = wp_parse_args( $options, array(
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        'status_subset' => array(),
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 'status_subset' => [],
</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">                parent::__construct( $start_date, $end_date, $options );
</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 ( 'any' === $status ) {
-                       $status = '';
-               }
-
-               if ( $status && $this->validate_status_input( $status ) ) {
-                       $this->status = $status;
-               }
-       }
-
-       /**
-        * Validate the given status ID string.
-        *
-        * @param string $status The status ID to filter for in the report.
-        *
-        * @return bool True if the status ID is valid. Otherwise false.
-        */
-       protected function validate_status_input( $status ) {
-               if ( is_array( $this->options['status_subset'] ) && ! empty( $this->options['status_subset'] ) ) {
-                       if ( ! in_array( $status, $this->options['status_subset'], true ) ) {
-                               $this->error->add( 'invalid_status', 'Please enter a valid status ID.' );
-
-                               return false;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         if ( $status && 'any' !== $status ) {
+                       try {
+                               $this->status = validate_wordcamp_status( $status, $options );
+                       } catch ( Exception $e ) {
+                               $this->error->add(
+                                       self::$slug . '-status-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">-
-                       return true;
</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 ( ! in_array( $status, array_keys( WordCamp_Loader::get_post_statuses() ), true ) ) {
-                       $this->error->add( 'invalid_status', 'Please enter a valid status ID.' );
-
-                       return false;
-               }
-
-               return true;
</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></span></pre></div>
<a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampreportsclassesutilityclassdaterangephp"></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/utility/class-date-range.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/utility/class-date-range.php                             (rev 0)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/classes/utility/class-date-range.php       2018-07-28 00:46:28 UTC (rev 7516)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,56 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+
+namespace WordCamp\Reports\Utility;
+defined( 'WPINC' ) || die();
+
+use DateTimeInterface, DateInterval;
+
+/**
+ * Class Date_Range
+ * @package WordCamp\Reports\Utility
+ */
+class Date_Range {
+       /**
+        * The start date of the range.
+        *
+        * @var DateTimeInterface|null
+        */
+       public $start = null;
+
+       /**
+        * The end date of the range.
+        *
+        * @var DateTimeInterface|null
+        */
+       public $end = null;
+
+       /**
+        * The interval between the start and end dates.
+        *
+        * @var DateInterval|null
+        */
+       public $interval = null;
+
+       /**
+        * Date_Range constructor.
+        *
+        * @param DateTimeInterface $start
+        * @param DateTimeInterface $end
+        */
+       public function __construct( DateTimeInterface $start, DateTimeInterface $end ) {
+               $this->start    = $start;
+               $this->end      = $end;
+               $this->interval = $end->diff( $start );
+       }
+
+       /**
+        * Test if a date is within the range.
+        *
+        * @param DateTimeInterface $date The date to test.
+        *
+        * @return bool
+        */
+       public function is_within( DateTimeInterface $date ) {
+               return $date >= $this->start && $date <= $this->end;
+       }
+}
</ins></span></pre></div>
<a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampreportsincludestimephp"></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/includes/time.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/includes/time.php                                (rev 0)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/includes/time.php  2018-07-28 00:46:28 UTC (rev 7516)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,144 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+
+namespace WordCamp\Reports\Time;
+defined( 'WPINC' ) || die();
+
+use Exception;
+use DateTimeImmutable;
+use WordCamp\Reports\Utility\Date_Range;
+
+/**
+ * Generate a simple array of years.
+ *
+ * @param int $start_year The first year in the array.
+ * @param int $end_year   The last year in the array.
+ *
+ * @return array
+ */
+function year_array( int $start_year, int $end_year ) {
+       return range( $start_year, $end_year, 1 );
+}
+
+/**
+ * Generate an associative array of quarters, with abbreviation keys and label values.
+ *
+ * @return array
+ */
+function quarter_array() {
+       return array(
+               'q1' => '1st quarter',
+               'q2' => '2nd quarter',
+               'q3' => '3rd quarter',
+               'q4' => '4th quarter',
+       );
+}
+
+/**
+ * Generate an associative array of months, with numerical keys and string values.
+ *
+ * @return array
+ */
+function month_array() {
+       $months = array();
+
+       foreach ( range( 1, 12 ) as $number ) {
+               $months[ $number ] = date( 'F', mktime( 0, 0, 0, $number, 10 ) );
+       }
+
+       return $months;
+}
+
+/**
+ * Convert a representation of a time period within a given year into a date range.
+ *
+ * @param int        $year   The year containing the time period.
+ * @param string|int $period The time period to convert. E.g. 2, 'February', 'q1'.
+ *
+ * @return Date_Range An object representing the valid date range.
+ * @throws Exception
+ */
+function convert_time_period_to_date_range( $year, $period = '' ) {
+       if ( ! is_int( $year ) ) {
+               throw new Exception( 'Invalid year.' );
+       }
+
+       $months = month_array();
+
+       $start_date = '';
+       $end_date   = '';
+
+       if ( ! $period || 'all' === $period ) {
+               // Period is the entire year.
+               $start_date = "$year-01-01";
+               $end_date   = "$year-12-31";
+       } elseif ( array_key_exists( $period, quarter_array() ) ) {
+               // Period is a quarter.
+               switch ( $period ) {
+                       case 'q1' :
+                               $start_date = "$year-01-01";
+                               break;
+                       case 'q2' :
+                               $start_date = "$year-04-01";
+                               break;
+                       case 'q3' :
+                               $start_date = "$year-07-01";
+                               break;
+                       case 'q4' :
+                               $start_date = "$year-10-01";
+                               break;
+               }
+
+               $end_date = date( 'Y-m-d', strtotime( '+ 3 months - 1 second', strtotime( $start_date ) ) );
+       } elseif ( array_key_exists( $period, $months ) || in_array( $period, $months, true ) ) {
+               // Period is a specific month.
+               if ( in_array( $period, $months, true ) ) {
+                       // Month name given. Convert it to a number.
+                       $period = array_search( $period, $months, true );
+               }
+
+               $start_date = "$year-$period-01";
+               $end_date   = date( 'Y-m-d', strtotime( '+ 1 month - 1 second', strtotime( $start_date ) ) );
+       }
+
+       if ( ! $start_date || ! $end_date ) {
+               throw new Exception( 'Invalid time period.' );
+       }
+
+       try {
+               $range = new Date_Range(
+                       new DateTimeImmutable( $start_date ),
+                       new DateTimeImmutable( $end_date )
+               );
+       } catch ( Exception $e ) {
+               throw new Exception( sprintf(
+                       'Invalid range: %s',
+                       $e->getMessage()
+               ) );
+       }
+
+       return $range;
+}
+
+/**
+ * Change the expiration time interval based on the current date/time relative to a date range.
+ *
+ * @param int        $expiration A time interval in seconds.
+ * @param Date_Range $range
+ *
+ * @return int A (possibly) modified time interval in seconds.
+ * @throws Exception
+ */
+function modify_cache_expiration_for_date_range( $expiration, Date_Range $range ) {
+       $now = new DateTimeImmutable( 'now' );
+       $now->setTime( 0, 0, 0 ); // Beginning of the current day.
+
+       if ( $range->is_within( $now ) ) {
+               // Expire the cache sooner if the data includes the current day.
+               $expiration = HOUR_IN_SECONDS;
+       } elseif ( $range->end->diff( $now )->y > 0 ) {
+               // Keep the cache longer if the end of the date range is over a year ago.
+               $expiration = MONTH_IN_SECONDS;
+       }
+
+       return $expiration;
+}
</ins></span></pre></div>
<a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampreportsincludesvalidationphp"></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/includes/validation.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/includes/validation.php                          (rev 0)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/includes/validation.php    2018-07-28 00:46:28 UTC (rev 7516)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,187 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+
+namespace WordCamp\Reports\Validation;
+defined( 'WPINC' ) || die();
+
+use Exception;
+use DateTime, DateTimeImmutable, DateInterval;
+use WP_Post;
+use WordCamp\Reports\Utility\Date_Range;
+use WordCamp_Loader;
+
+/**
+ * Validate strings for start and end dates in a date range.
+ *
+ * @param string $start_date A string representation of the beginning of the date range.
+ * @param string $end_date   A string representation of the end of the date range.
+ * @param array  $config     {
+ *     Optional. Modify the default configuration of the validator.
+ *
+ *     @type bool              $allow_future_start True to allow the date range to start in the future. Default false.
+ *     @type bool              $allow_future_end   True to allow the date range to extend into the future. Default true.
+ *     @type DateTime|null     $earliest_start     The earliest date that can be used for the start of the date range.
+ *     @type DateInterval|null $max_interval       The maximum interval of time between the start and end dates.
+ *     @type bool              $include_end_date   True to include the full ending day in the date range.
+ * }
+ *
+ * @return Date_Range An object representing the valid date range.
+ * @throws Exception
+ */
+function validate_date_range( $start_date, $end_date, array $config = [] ) {
+       $config_defaults = [
+               'allow_future_start' => false,
+               'allow_future_end'   => true,
+               'earliest_start'     => null,
+               'max_interval'       => new DateInterval( 'P1Y' ),
+               'include_end_date'   => true,
+       ];
+
+       $config = wp_parse_args( $config, $config_defaults );
+
+       if ( ! $start_date || ! $end_date ) {
+               throw new Exception( 'Please enter valid start and end dates.' );
+       }
+
+       try {
+               $start_date = new DateTimeImmutable( $start_date ); // Immutable so methods don't modify the original object.
+       } catch ( Exception $e ) {
+               throw new Exception( sprintf(
+                       'Invalid start date: %s',
+                       $e->getMessage()
+               ) );
+       }
+
+       // No future start dates.
+       if ( ! $config['allow_future_start'] && $start_date > date_create( 'now' ) ) {
+               throw new Exception( 'Please enter a start date that is the same as or before today\'s date.' );
+       }
+
+       // Check for start date boundary.
+       if ( $config['earliest_start'] instanceof DateTime && $start_date < $config['earliest_start'] ) {
+               throw new Exception( sprintf(
+                       'Please enter a start date of %s or later.',
+                       $config['earliest_start']->format( 'Y-m-d' )
+               ) );
+       }
+
+       try {
+               $end_date = new DateTimeImmutable( $end_date ); // Immutable so methods don't modify the original object.
+       } catch ( Exception $e ) {
+               throw new Exception( sprintf(
+                       'Invalid end date: %s',
+                       $e->getMessage()
+               ) );
+       }
+
+       // No negative date intervals.
+       if ( $start_date > $end_date ) {
+               throw new Exception( 'Please enter an end date that is the same as or after the start date.' );
+       }
+
+       // Check for date interval boundary.
+       if ( $config['max_interval'] instanceof DateInterval ) {
+               $max_end_date = $start_date->add( $config['max_interval'] );
+
+               if ( $end_date > $max_end_date ) {
+                       throw new Exception( sprintf(
+                               'Please enter an end date that is no more than %s days after the start date.',
+                               $start_date->diff( $max_end_date )->format( '%a' )
+                       ) );
+               }
+       }
+
+       // If the end date doesn't have a specific time, make sure the entire day is included.
+       if ( $config['include_end_date'] && '00:00:00' === $end_date->format( 'H:i:s' ) ) {
+               $end_date->setTime( 23, 59, 59 );
+       }
+
+       return new Date_Range( $start_date, $end_date );
+}
+
+/**
+ * Validate a WordCamp post ID.
+ *
+ * @param int   $post_id The ID of a WCPT post.
+ * @param array $config  {
+ *     Optional. Modify the default configuration of the validator.
+ *
+ *     @type bool $require_site True if the WordCamp post must have an associated site in the network.
+ * }
+ *
+ * @return array An associative array containing valid post ID and site ID integers for the WordCamp.
+ * @throws Exception
+ */
+function validate_wordcamp_id( $post_id, array $config = [] ) {
+       $config_defaults = [
+               'require_site' => true,
+       ];
+
+       $config = wp_parse_args( $config, $config_defaults );
+
+       $switched = false;
+
+       if ( BLOG_ID_CURRENT_SITE !== get_current_blog_id() ) {
+               $switched = switch_to_blog( BLOG_ID_CURRENT_SITE );
+       }
+
+       $wordcamp_post = get_post( $post_id );
+
+       if ( ! $wordcamp_post instanceof WP_Post || WCPT_POST_TYPE_ID !== get_post_type( $wordcamp_post ) ) {
+               throw new Exception( 'Please enter a valid WordCamp ID.' );
+       }
+
+       $valid = [
+               'post_id' => $post_id,
+               'site_id' => 0,
+       ];
+
+       if ( $config['require_site'] ) {
+               $site_id = get_wordcamp_site_id( $wordcamp_post );
+
+               if ( ! $site_id ) {
+                       throw new Exception( 'The specified WordCamp does not have a site yet.' );
+               }
+
+               $valid['site_id'] = $site_id;
+       }
+
+       if ( $switched ) {
+               restore_current_blog();
+       }
+
+       return $valid;
+}
+
+/**
+ * Validate a WordCamp status ID string.
+ *
+ * @param string $wordcamp_status A WordCamp status ID string.
+ * @param array  $config          {
+ *     Optional. Modify the default configuration of the validator.
+ *
+ *     @type array $status_subset An array of status ID strings that should be considered valid.
+ * }
+ *
+ * @return string The validated WordCamp status ID string.
+ * @throws Exception
+ */
+function validate_wordcamp_status( $wordcamp_status, array $config = [] ) {
+       $config_defaults = [
+               'status_subset' => [],
+       ];
+
+       $config = wp_parse_args( $config, $config_defaults );
+
+       $valid_statuses = array_keys( WordCamp_Loader::get_post_statuses() );
+       $subset         = array_intersect( $config['status_subset'], $valid_statuses );
+
+       if ( ! empty( $subset ) ) {
+               $valid_statuses = $subset;
+       }
+
+       if ( ! in_array( $wordcamp_status, $valid_statuses, true ) ) {
+               throw new Exception( 'Please enter a valid status ID.' );
+       }
+
+       return $wordcamp_status;
+}
</ins></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-07-27 17:02:38 UTC (rev 7515)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/index.php  2018-07-28 00:46:28 UTC (rev 7516)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -31,6 +31,15 @@
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * Get the path for the includes directory.
+ *
+ * @return string Path with trailing slash.
+ */
+function get_includes_dir_path() {
+       return trailingslashit( PLUGIN_DIR ) . 'includes/';
+}
+
+/**
</ins><span class="cx" style="display: block; padding: 0 10px">  * Get the path for the views directory.
</span><span class="cx" style="display: block; padding: 0 10px">  *
</span><span class="cx" style="display: block; padding: 0 10px">  * @return string Path with trailing slash.
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -58,6 +67,21 @@
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * Autoload all the files in the includes directory.
+ *
+ * @return void
+ */
+function load_includes() {
+       foreach ( glob( get_includes_dir_path() . '*.php' ) as $filename ) {
+               if ( is_readable( $filename ) ) {
+                       include_once ( $filename );
+               }
+       }
+}
+
+add_action( 'plugins_loaded', __NAMESPACE__ . '\load_includes' );
+
+/**
</ins><span class="cx" style="display: block; padding: 0 10px">  * Autoloader for plugin classes.
</span><span class="cx" style="display: block; padding: 0 10px">  *
</span><span class="cx" style="display: block; padding: 0 10px">  * @param string $class The fully-qualified class name.
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -109,6 +133,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                __NAMESPACE__ . '\Report\Sponsor_Invoices',
</span><span class="cx" style="display: block; padding: 0 10px">                __NAMESPACE__ . '\Report\Payment_Activity',
</span><span class="cx" style="display: block; padding: 0 10px">                __NAMESPACE__ . '\Report\Sponsorship_Grants',
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                __NAMESPACE__ . '\Report\WordCamp_Details',
</ins><span class="cx" style="display: block; padding: 0 10px">                 __NAMESPACE__ . '\Report\WordCamp_Status',
</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\WordCamp_Payment_Methods',
</span></span></pre></div>
<a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampreportsviewsreportwordcampdetailsphp"></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/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/views/report/wordcamp-details.php                                (rev 0)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/views/report/wordcamp-details.php  2018-07-28 00:46:28 UTC (rev 7516)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,70 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/**
+ * @package WordCamp\Reports
+ */
+
+namespace WordCamp\Reports\Views\Report\WordCamp_Details;
+defined( 'WPINC' ) || die();
+
+use WordCamp\Reports;
+use WordCamp\Reports\Report;
+
+/** @var string $start_date */
+/** @var string $end_date */
+/** @var bool   $include_dateless */
+/** @var string $status */
+/** @var array  $statuses */
+?>
+
+<div class="wrap">
+       <h1>
+               <a href="<?php echo esc_attr( Reports\get_page_url() ); ?>">WordCamp Reports</a>
+               &raquo;
+               <?php echo esc_html( Report\WordCamp_Details::$name ); ?>
+       </h1>
+
+       <?php echo wpautop( wp_kses_post( Report\WordCamp_Details::$description ) ); ?>
+
+       <h4>Methodology</h4>
+
+       <?php echo wpautop( wp_kses_post( Report\WordCamp_Details::$methodology ) ); ?>
+
+       <form method="post" action="">
+               <input type="hidden" name="action" value="run-report" />
+               <?php wp_nonce_field( 'run-report', Report\WordCamp_Details::$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="include_dateless">Include WordCamps without a date</label></th>
+                               <td><input type="checkbox" id="include_dateless" name="include_dateless"<?php checked( $include_dateless ); ?> /></td>
+                       </tr>
+                       <tr>
+                               <th scope="row"><label for="status">Status (optional)</label></th>
+                               <td>
+                                       <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_attr( $label ); ?></option>
+                                               <?php endforeach; ?>
+                                       </select>
+                               </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( 'Export CSV', 'primary', 'action', false ); ?>
+       </form>
+</div>
</ins></span></pre>
</div>
</div>

</body>
</html>