<!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>[7550] sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports: WordCamp Reports: Improvements to 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/7550">7550</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/7550","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-31 04:11:25 +0000 (Tue, 31 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: Improvements to WordCamp Details report

* Add fields for the totals of Tickets, Speakers, Sponsors, and Organizers for each WordCamp in the data set.
* Add UI for selecting which fields will be included in the spreadsheet
* Make sure cached data sets are differentiated by whether they include dateless WordCamps and/or post counts</pre>

<h3>Modified 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><a href="#sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampreportsclassesutilityclassdaterangephp">sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/classes/utility/class-date-range.php</a></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_htmlwpcontentpluginswordcampreportsviewsreportwordcampdetailsphp">sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/views/report/wordcamp-details.php</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li><a href="#sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampreportsassetscsswordcampdetailscss">sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/assets/css/wordcamp-details.css</a></li>
<li><a href="#sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampreportsassetsjswordcampdetailsjs">sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/assets/js/wordcamp-details.js</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampreportsassetscsswordcampdetailscss"></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/assets/css/wordcamp-details.css</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/assets/css/wordcamp-details.css                          (rev 0)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/assets/css/wordcamp-details.css    2018-07-31 04:11:25 UTC (rev 7550)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,75 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+.fields-container {
+       background-color: #fff;
+       border: 1px solid #e5e5e5;
+       box-shadow: 0 1px 1px rgba(0,0,0,.04);
+       position: relative;
+       margin: 55px 0 30px;
+}
+
+.field-checkbox:nth-child(2n+2) {
+       background-color: #f9f9f9;
+}
+
+@media screen and ( min-width: 450px ) {
+       .fields-container {
+               display: grid;
+               grid-template-columns: 1fr 1fr;
+       }
+       .field-checkbox:nth-child(2n+2) {
+               background-color: transparent;
+       }
+       .field-checkbox:nth-child(4n+2),
+       .field-checkbox:nth-child(4n+3) {
+               background-color: #f9f9f9;
+       }
+}
+
+@media screen and ( min-width: 700px ) {
+       .fields-container {
+               grid-template-columns: 1fr 1fr 1fr 1fr;
+       }
+       .field-checkbox:nth-child(4n+2),
+       .field-checkbox:nth-child(4n+3) {
+               background-color: transparent;
+       }
+       .field-checkbox:nth-child(8n+2),
+       .field-checkbox:nth-child(8n+3),
+       .field-checkbox:nth-child(8n+4),
+       .field-checkbox:nth-child(8n+5) {
+               background-color: #f9f9f9;
+       }
+}
+
+.fields-label {
+       text-align: left;
+       line-height: 1.3;
+       font-weight: 600;
+       font-size: 14px;
+       padding: 20px 10px 20px 0;
+       position: absolute;
+       top: -55px;
+}
+
+.fields-checkall {
+       display: inline-block;
+       margin-left: 3em;
+       font-size: 12px;
+       font-weight: normal;
+       font-style: italic;
+}
+
+.field-checkbox {
+       display: flex;
+       flex-direction: row;
+       align-items: center;
+       padding: 1.5em 0;
+}
+
+.fields-container input[type="checkbox"] {
+       flex: 1;
+}
+
+.fields-container label {
+       flex: 4;
+}
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of file
</span></span></pre></div>
<a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampreportsassetsjswordcampdetailsjs"></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/assets/js/wordcamp-details.js</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/assets/js/wordcamp-details.js                            (rev 0)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/assets/js/wordcamp-details.js      2018-07-31 04:11:25 UTC (rev 7550)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,74 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+( function( window, $ ) {
+
+       'use strict';
+
+       var WordCampDetails = window.WordCampDetails || {};
+
+       $.extend( WordCampDetails, {
+               /**
+                * Initialize the script.
+                */
+               init: function() {
+                       var self = this;
+
+                       this.cache = {
+                               $container: $( '.fields-container' ),
+                               $control: $()
+                       };
+
+                       self.cache.$control = self.createControl();
+
+                       $( document ).ready( function() {
+                               self.cache.$container.find( 'legend' ).append( self.cache.$control );
+                       } );
+               },
+
+               /**
+                * Create the elements for the checkbox control that will toggle all of the checkboxes in the form.
+                *
+                * @return {jQuery} The object for the control component.
+                */
+               createControl: function() {
+                       var self = this,
+                               $input, $control;
+
+                       $input = $( '<input>' )
+                               .attr( 'type', 'checkbox' )
+                       ;
+
+                       $input.on( 'change', function( event ) {
+                               var $target = $( event.target );
+
+                               self.toggleCheckboxes( $target );
+                       } );
+
+                       $control = $( '<label>' )
+                               .addClass( 'fields-checkall' )
+                               .text( 'Check all' )
+                               .prepend( $input )
+                       ;
+
+                       return $control;
+               },
+
+               /**
+                * Perform the checking/unchecking of all the checkboxes.
+                *
+                * @param {jQuery} $target The control.
+                */
+               toggleCheckboxes: function( $target ) {
+                       var $container = $target.parents( '.fields-container' ),
+                               $checkboxes = $container.find( 'input[type="checkbox"]' ).not( ':disabled' );
+
+                       if ( $target.is( ':checked' ) ) {
+                               $checkboxes.prop( 'checked', true );
+                       } else {
+                               $checkboxes.prop( 'checked', false );
+                       }
+               }
+       } );
+
+       WordCampDetails.init();
+
+} )( window, jQuery );
</ins></span></pre></div>
<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-07-31 04:04:38 UTC (rev 7549)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/classes/report/class-wordcamp-details.php  2018-07-31 04:11:25 UTC (rev 7550)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -8,12 +8,10 @@
</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;
-use WordCamp\Reports;
-use WordCamp\Reports\Report\WordCamp_Status;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+use WP_Post, WP_Query;
+use function WordCamp\Reports\{get_assets_url, get_assets_dir_path, get_views_dir_path};
</ins><span class="cx" style="display: block; padding: 0 10px"> use WordCamp\Reports\Utility\Date_Range;
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-use function WordCamp\Reports\Validation\{validate_date_range, validate_wordcamp_status};
-use function WordCamp\Reports\Time\modify_cache_expiration_for_date_range;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+use function WordCamp\Reports\Validation\{validate_date_range, validate_wordcamp_id, validate_wordcamp_status};
</ins><span class="cx" style="display: block; padding: 0 10px"> use WordCamp_Admin, 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">@@ -54,7 +52,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">        public static $methodology = "
</span><span class="cx" style="display: block; padding: 0 10px">                <ol>
</span><span class="cx" style="display: block; padding: 0 10px">                        <li>Retrieve WordCamp posts that fit within the date range and other optional criteria.</li>
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        <li>Extract the post meta values for each post that match the fields requested.</li>
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 <li>Extract the data for each post that match the fields requested.</li>
</ins><span class="cx" style="display: block; padding: 0 10px">                         <li>Walk all of the extracted data and format it for display.</li>
</span><span class="cx" style="display: block; padding: 0 10px">                </ol>
</span><span class="cx" style="display: block; padding: 0 10px">        ";
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -81,13 +79,20 @@
</span><span class="cx" style="display: block; padding: 0 10px">        public $status = '';
</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">-         * The fields to include in the report output.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+  * Whether to include data for WordCamps that don't have a date set.
</ins><span class="cx" style="display: block; padding: 0 10px">          *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         * @var array
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+  * @var bool
</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 $fields = [];
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ public $include_dateless = false;
</ins><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">+         * Whether to include counts of various post types for each WordCamp.
+        *
+        * @var bool
+        */
+       public $include_counts = false;
+
+       /**
</ins><span class="cx" style="display: block; padding: 0 10px">          * Data fields that can be visible in a public context.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @var array An associative array of key/default value pairs.
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -104,22 +109,25 @@
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><span class="cx" style="display: block; padding: 0 10px">         * WordCamp_Details constructor.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         * @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    {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+  * @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 bool   $include_dateless Optional. True to include data for WordCamps that don't have a date set. Default false.
+        * @param bool   $include_counts   Optional. True to include counts of various post types for each WordCamp. Default false.
+        * @param array  $options          {
</ins><span class="cx" style="display: block; padding: 0 10px">          *     Optional. Additional report parameters.
</span><span class="cx" style="display: block; padding: 0 10px">         *     See Base::__construct and the functions in WordCamp\Reports\Validation for additional parameters.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         *     @type bool $include_dateless True to include WordCamps that don't have a date set. Default false.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+  *     @type array $status_subset A list of valid status IDs.
+        *     @type array $fields        Not implemented yet.
</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">-        public function __construct( $start_date, $end_date, $status = '', array $fields = [], array $options = [] ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ public function __construct( $start_date, $end_date, $status = '', $include_dateless = false, $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, array(
-                       'include_dateless' => false,
-               ) );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $options = wp_parse_args( $options, [
+                       'status_subset' => [],
+                       'fields'        => [],
+               ] );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                parent::__construct( $options );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -143,8 +151,32 @@
</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">+                $this->include_dateless = wp_validate_boolean( $include_dateless );
+               $this->include_counts   = wp_validate_boolean( $include_counts );
+
+               $public_data_field_keys = array_merge(
+                       [
+                               'Name',
+                               'Status',
+                       ],
+                       WordCamp_Loader::get_public_meta_keys()
+               );
+               $this->public_data_fields = array_fill_keys( $public_data_field_keys, '' );
+
+               $private_data_field_keys = array_merge(
+                       [
+                               'ID',
+                               'Tickets',
+                               'Speakers',
+                               'Sponsors',
+                               'Organizers',
+                       ],
+                       array_diff( $this->get_meta_keys(), array_keys( $this->public_data_fields ) )
+               );
+               $this->private_data_fields = array_fill_keys( $private_data_field_keys, '' );
+
</ins><span class="cx" style="display: block; padding: 0 10px">                 try {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        $this->fields = $this->validate_fields_input( $fields );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 $this->options['fields'] = $this->validate_fields_input( $this->options['fields'] );
</ins><span class="cx" style="display: block; padding: 0 10px">                 } catch ( Exception $e ) {
</span><span class="cx" style="display: block; padding: 0 10px">                        $this->error->add(
</span><span class="cx" style="display: block; padding: 0 10px">                                self::$slug . '-fields-error',
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -151,29 +183,32 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                $e->getMessage()
</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">-
-               $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 )
-               ), '' );
</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">-         * TODO
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+  * Check the array of fields to include in the spreadsheet against the safelist of data fields.
</ins><span class="cx" style="display: block; padding: 0 10px">          *
</span><span class="cx" style="display: block; padding: 0 10px">         * @param array $fields
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         *
+        * @return array The validated fields.
+        * @throws Exception
</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 ) {}
</del><ins style="background-color: #dfd; 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 );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; 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 )
+                               ) );
+                       }
+               }
+
+               return $fields;
+       }
+
</ins><span class="cx" style="display: block; padding: 0 10px">         /**
</span><span class="cx" style="display: block; padding: 0 10px">         * Generate a cache key.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -180,13 +215,24 @@
</span><span class="cx" style="display: block; padding: 0 10px">         * @return string
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="cx" style="display: block; padding: 0 10px">        protected function get_cache_key() {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $cache_key = parent::get_cache_key() . '_' . $this->range->start->getTimestamp() . '-' . $this->range->end->getTimestamp();
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $cache_key_segments = [
+                       parent::get_cache_key(),
+                       $this->range->generate_cache_key_segment(),
+               ];
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                if ( $this->status ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        $cache_key .= '_' . $this->status;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 $cache_key_segments[] = $this->status;
</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 $cache_key;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         if ( $this->include_dateless ) {
+                       $cache_key_segments[] = '+dateless';
+               }
+
+               if ( $this->include_counts ) {
+                       $cache_key_segments[] = '+counts';
+               }
+
+               return implode( '_', $cache_key_segments );
</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">@@ -195,23 +241,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">         * @return int A time interval in seconds.
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="cx" style="display: block; padding: 0 10px">        protected function get_cache_expiration() {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $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;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         return $this->range->generate_cache_duration( parent::get_cache_expiration() );
</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">@@ -236,10 +266,17 @@
</span><span class="cx" style="display: block; padding: 0 10px">                $wordcamp_posts = $this->get_wordcamp_posts();
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                foreach ( $wordcamp_posts as $post ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        $data[] = $this->extract_wordcamp_fields( $post );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 $data[] = $this->fill_data_row( $post );
</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">                $data = $this->filter_data_fields( $data );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+               // 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 );
+               } );
+
</ins><span class="cx" style="display: block; padding: 0 10px">                 $this->maybe_cache_data( $data );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                return $data;
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -259,6 +296,42 @@
</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 full list of fields in the order they should appear in.
+        *
+        * @return array
+        */
+       protected static function get_field_order() {
+               /* @var WordCamp_Admin $wordcamp_admin */
+               global $wordcamp_admin;
+
+               return array_merge(
+                       [
+                               'ID',
+                               'Name',
+                       ],
+                       array_keys( $wordcamp_admin->meta_keys( 'wordcamp' ) ),
+                       [
+                               'Status',
+                               'Tickets',
+                               'Speakers',
+                               'Sponsors',
+                               'Organizers',
+                       ],
+                       array_keys( $wordcamp_admin->meta_keys( 'contributor' ) ),
+                       array_keys( $wordcamp_admin->meta_keys( 'organizer' ) ),
+                       array_keys( $wordcamp_admin->meta_keys( 'venue' ) ),
+                       [
+                               '_venue_coordinates',
+                               '_venue_city',
+                               '_venue_state',
+                               '_venue_country_code',
+                               '_venue_country_name',
+                               '_venue_zip',
+                       ]
+               );
+       }
+
+       /**
</ins><span class="cx" style="display: block; padding: 0 10px">          * Format the data for human-readable display.
</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 prepare.
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -270,10 +343,21 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                array_walk( $data, function( &$row ) use ( $all_statuses ) {
</span><span class="cx" style="display: block; padding: 0 10px">                        foreach ( $row as $key => $value ) {
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                if ( ! in_array( $key, $this->options['fields'], true ) ) {
+                                       unset( $row[ $key ] );
+                                       continue;
+                               }
+
</ins><span class="cx" style="display: block; padding: 0 10px">                                 switch ( $key ) {
</span><span class="cx" style="display: block; padding: 0 10px">                                        case 'Status':
</span><span class="cx" style="display: block; padding: 0 10px">                                                $row[ $key ] = $all_statuses[ $value ];
</span><span class="cx" style="display: block; padding: 0 10px">                                                break;
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                        case 'Tickets':
+                                       case 'Speakers':
+                                       case 'Sponsors':
+                                       case 'Organizers':
+                                               $row[ $key ] = number_format_i18n( $value );
+                                               break;
</ins><span class="cx" style="display: block; padding: 0 10px">                                         case 'Start Date (YYYY-mm-dd)':
</span><span class="cx" style="display: block; padding: 0 10px">                                        case 'End Date (YYYY-mm-dd)':
</span><span class="cx" style="display: block; padding: 0 10px">                                        case 'Contributor Day Date (YYYY-mm-dd)':
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -320,7 +404,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"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                if ( $this->options['include_dateless'] ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         if ( $this->include_dateless ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                         $post_args['meta_query'] = array_merge( $post_args['meta_query'], [
</span><span class="cx" style="display: block; padding: 0 10px">                                'relation' => 'OR',
</span><span class="cx" style="display: block; padding: 0 10px">                                [
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -333,6 +417,14 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                        'value'   => '',
</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">+
+                       // Don't include really old camps with no date or ones that didn't exist during the date range.
+                       $post_args['date_query'] = [
+                               [
+                                       'before' => $this->range->end->format( 'Y-m-d' ),
+                                       'after'  => $this->range->start->format( 'Y-m-d' ) . ' - 1 year',
+                               ],
+                       ];
</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">@@ -366,8 +458,8 @@
</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 extract_wordcamp_fields( WP_Post $wordcamp ) {
-               $meta_keys = $this->get_meta_keys();
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ protected function fill_data_row( WP_Post $wordcamp ) {
+               $meta_keys   = $this->get_meta_keys();
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $row = [
</span><span class="cx" style="display: block; padding: 0 10px">                        'ID'     => $wordcamp->ID,
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -375,6 +467,10 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        'Status' => $wordcamp->post_status,
</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">+                if ( $this->include_counts ) {
+                       $row = array_merge( $row, $this->get_counts( $wordcamp ) );
+               }
+
</ins><span class="cx" style="display: block; padding: 0 10px">                 foreach ( $meta_keys as $key ) {
</span><span class="cx" style="display: block; padding: 0 10px">                        $row[ $key ] = get_post_meta( $wordcamp->ID, $key, true ) ?: '';
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -403,6 +499,90 @@
</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">+         * Count the number of various post types for a WordCamp.
+        *
+        * @param WP_Post $wordcamp
+        *
+        * @return array
+        */
+       protected function get_counts( WP_Post $wordcamp ) {
+               $counts = [
+                       'Tickets'    => 0,
+                       'Speakers'   => 0,
+                       'Sponsors'   => 0,
+                       'Organizers' => 0,
+               ];
+
+               try {
+                       $ids = validate_wordcamp_id( $wordcamp->ID, [ 'require_site' => true ] );
+               } catch ( Exception $e ) {
+                       return $counts;
+               }
+
+               $get_count = function( $post_type ) {
+                       $posts = new WP_Query( [
+                               'posts_per_page' => 1, // Only need to fetch 1 to populate total number in found_posts.
+                               'post_type'      => $post_type,
+                               'post_status'    => 'publish',
+                       ] );
+
+                       return absint( $posts->found_posts );
+               };
+
+               switch_to_blog( $ids['site_id'] );
+
+               // Tickets
+               $stats = get_option( 'camptix_stats' );
+               if ( isset( $stats['sold'] ) && ! empty( $stats['sold'] ) ) {
+                       $counts['Tickets'] = absint( $stats['sold'] );
+               }
+
+               // Others
+               $counts['Speakers']   = $get_count( 'wcb_speaker' );
+               $counts['Sponsors']   = $get_count( 'wcb_sponsor' );
+               $counts['Organizers'] = $get_count( 'wcb_organizer' );
+
+               restore_current_blog();
+
+               return $counts;
+       }
+
+       /**
+        * Register all assets used by this report.
+        *
+        * @return void
+        */
+       protected 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 );
+       }
+
+       /**
</ins><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">@@ -417,7 +597,20 @@
</span><span class="cx" style="display: block; padding: 0 10px">                $nonce            = filter_input( INPUT_POST, self::$slug . '-nonce' );
</span><span class="cx" style="display: block; padding: 0 10px">                $statuses         = WordCamp_Loader::get_post_statuses();
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                include Reports\get_views_dir_path() . 'report/wordcamp-details.php';
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $field_order      = array_fill_keys( self::get_field_order(), '' );
+               $field_defaults   = array_replace( $field_order, [
+                       'ID'                      => 'checked',
+                       'Name'                    => 'checked disabled',
+                       'Start Date (YYYY-mm-dd)' => 'checked',
+                       'End Date (YYYY-mm-dd)'   => 'checked',
+                       'Location'                => 'checked',
+                       'URL'                     => 'checked',
+               ] );
+
+               $shadow_report    = new self( '', '', '', false, false, [ 'public' => false ] );
+               $available_fields = array_intersect_key( $field_defaults, $shadow_report->get_data_fields_safelist() );
+
+               include get_views_dir_path() . 'report/wordcamp-details.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">@@ -430,6 +623,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                $end_date         = filter_input( INPUT_POST, 'end-date' );
</span><span class="cx" style="display: block; padding: 0 10px">                $include_dateless = filter_input( INPUT_POST, 'include_dateless', FILTER_VALIDATE_BOOLEAN );
</span><span class="cx" style="display: block; padding: 0 10px">                $status           = filter_input( INPUT_POST, 'status' );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                $fields           = filter_input( INPUT_POST, 'fields', FILTER_SANITIZE_STRING, [ 'flags' => FILTER_REQUIRE_ARRAY ] );
</ins><span class="cx" style="display: block; padding: 0 10px">                 $refresh          = filter_input( INPUT_POST, 'refresh', FILTER_VALIDATE_BOOLEAN );
</span><span class="cx" style="display: block; padding: 0 10px">                $action           = filter_input( INPUT_POST, 'action' );
</span><span class="cx" style="display: block; padding: 0 10px">                $nonce            = filter_input( INPUT_POST, self::$slug . '-nonce' );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -441,10 +635,19 @@
</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">                if ( wp_verify_nonce( $nonce, 'run-report' ) && current_user_can( 'manage_network' ) ) {
</span><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;
+                       }
+
+                       // 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">                         $options = array(
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                'public'           => false,
-                               'include_dateless' => $include_dateless,
-                               'earliest_start'   => new DateTime( '2006-01-01' ), // No WordCamp posts before 2006.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         'fields'         => $fields,
+                               'public'         => false,
+                               'earliest_start' => new DateTime( '2006-01-01' ), // No WordCamp posts before 2006.
</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 ( $status ) {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -455,7 +658,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                $options['flush_cache'] = true;
</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">-                        $report = new self( $start_date, $end_date, $status, [], $options );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 $report = new self( $start_date, $end_date, $status, $include_dateless, $include_counts, $options );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                        $filename = [ $report::$name ];
</span><span class="cx" style="display: block; padding: 0 10px">                        $filename[] = $report->range->start->format( 'Y-m-d' );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -463,6 +666,12 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        if ( $report->status ) {
</span><span class="cx" style="display: block; padding: 0 10px">                                $filename[] = $report->status;
</span><span class="cx" style="display: block; padding: 0 10px">                        }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                        if ( $report->include_dateless ) {
+                               $filename[] = 'include-dateless';
+                       }
+                       if ( $report->include_counts ) {
+                               $filename[] = 'include-counts';
+                       }
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                        $data = $report->prepare_data_for_display( $report->get_data() );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span></span></pre></div>
<a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampreportsclassesutilityclassdaterangephp"></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/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     2018-07-31 04:04:38 UTC (rev 7549)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/classes/utility/class-date-range.php       2018-07-31 04:11:25 UTC (rev 7550)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -3,7 +3,8 @@
</span><span class="cx" style="display: block; padding: 0 10px"> namespace WordCamp\Reports\Utility;
</span><span class="cx" style="display: block; padding: 0 10px"> defined( 'WPINC' ) || die();
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-use DateTimeInterface, DateInterval;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+use Exception;
+use DateTimeInterface, DateTime, DateTimeImmutable, DateInterval;
</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">  * Class Date_Range
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -13,7 +14,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><span class="cx" style="display: block; padding: 0 10px">         * The start date of the range.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         * @var DateTimeInterface|null
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+  * @var DateTimeImmutable|null
</ins><span class="cx" style="display: block; padding: 0 10px">          */
</span><span class="cx" style="display: block; padding: 0 10px">        public $start = null;
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -20,7 +21,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><span class="cx" style="display: block; padding: 0 10px">         * The end date of the range.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         * @var DateTimeInterface|null
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+  * @var DateTimeImmutable|null
</ins><span class="cx" style="display: block; padding: 0 10px">          */
</span><span class="cx" style="display: block; padding: 0 10px">        public $end = null;
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -38,8 +39,16 @@
</span><span class="cx" style="display: block; padding: 0 10px">         * @param DateTimeInterface $end
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="cx" style="display: block; padding: 0 10px">        public function __construct( DateTimeInterface $start, DateTimeInterface $end ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $this->start    = $start;
-               $this->end      = $end;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         if ( $start instanceof DateTime ) {
+                       $start = DateTimeImmutable::createFromMutable( $start );
+               }
+               $this->start = $start;
+
+               if ( $end instanceof DateTime ) {
+                       $end = DateTimeImmutable::createFromMutable( $end );
+               }
+               $this->end = $end;
+
</ins><span class="cx" style="display: block; padding: 0 10px">                 $this->interval = $end->diff( $start );
</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">@@ -53,4 +62,40 @@
</span><span class="cx" style="display: block; padding: 0 10px">        public function is_within( DateTimeInterface $date ) {
</span><span class="cx" style="display: block; padding: 0 10px">                return $date >= $this->start && $date <= $this->end;
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+       /**
+        * Generate a standardized string representation of the date range for use in a cache key.
+        *
+        * @return string
+        */
+       public function generate_cache_key_segment() {
+               return $this->start->getTimestamp() . '-' . $this->end->getTimestamp();
+       }
+
+       /**
+        * Modify a cache key duration based on the date range compared to the current time.
+        *
+        * @param int $duration Duration of cache key in seconds.
+        *
+        * @return int
+        */
+       public function generate_cache_duration( $duration ) {
+               try {
+                       $now = new DateTimeImmutable( 'now' );
+               } catch ( Exception $e ) {
+                       return $duration;
+               }
+
+               $now->setTime( 0, 0, 0 ); // Beginning of the current day.
+
+               if ( $this->is_within( $now ) ) {
+                       // Expire the cache sooner if the data includes the current day.
+                       $duration = HOUR_IN_SECONDS;
+               } elseif ( $this->end->diff( $now )->y > 0 ) {
+                       // Keep the cache longer if the end of the date range is over a year ago.
+                       $duration = MONTH_IN_SECONDS;
+               }
+
+               return $duration;
+       }
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span></span></pre></div>
<a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampreportsincludestimephp"></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/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        2018-07-31 04:04:38 UTC (rev 7549)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/includes/time.php  2018-07-31 04:11:25 UTC (rev 7550)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -4,8 +4,8 @@
</span><span class="cx" style="display: block; padding: 0 10px"> defined( 'WPINC' ) || die();
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> use Exception;
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-use DateTimeImmutable;
</del><span class="cx" style="display: block; padding: 0 10px"> use WordCamp\Reports\Utility\Date_Range;
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+use function WordCamp\Reports\Validation\validate_date_range;
</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">  * Generate a simple array of years.
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -105,10 +105,9 @@
</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">        try {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $range = new Date_Range(
-                       new DateTimeImmutable( $start_date ),
-                       new DateTimeImmutable( $end_date )
-               );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $range = validate_date_range( $start_date, $end_date, [
+                       'allow_future_start' => true,
+               ] );
</ins><span class="cx" style="display: block; padding: 0 10px">         } catch ( Exception $e ) {
</span><span class="cx" style="display: block; padding: 0 10px">                throw new Exception( sprintf(
</span><span class="cx" style="display: block; padding: 0 10px">                        'Invalid range: %s',
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -118,27 +117,3 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        return $range;
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-
-/**
- * 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;
-}
</del></span></pre></div>
<a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampreportsviewsreportwordcampdetailsphp"></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/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        2018-07-31 04:04:38 UTC (rev 7549)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/views/report/wordcamp-details.php  2018-07-31 04:11:25 UTC (rev 7550)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -14,6 +14,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> /** @var bool   $include_dateless */
</span><span class="cx" style="display: block; padding: 0 10px"> /** @var string $status */
</span><span class="cx" style="display: block; padding: 0 10px"> /** @var array  $statuses */
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+/** @var array  $available_fields */
</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"> <div class="wrap">
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -65,6 +66,25 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        </tbody>
</span><span class="cx" style="display: block; padding: 0 10px">                </table>
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                <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>
+
</ins><span class="cx" style="display: block; padding: 0 10px">                 <?php submit_button( 'Export CSV', 'primary', 'action', false ); ?>
</span><span class="cx" style="display: block; padding: 0 10px">        </form>
</span><span class="cx" style="display: block; padding: 0 10px"> </div>
</span></span></pre>
</div>
</div>

</body>
</html>