<!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>[7573] sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports: WordCamp Reports: Add report for meetup events</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/7573">7573</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/7573","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-08-01 00:17:32 +0000 (Wed, 01 Aug 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 report for meetup events

Specify a date range and get information on the number of events during the
time period, sorted by country and by meetup group.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampreportsclassesreportclassmeetupgroupsphp">sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/classes/report/class-meetup-groups.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_htmlwpcontentpluginswordcampreportsindexphp">sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/index.php</a></li>
<li><a href="#sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampreportsviewshtmlmeetupgroupsphp">sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/views/html/meetup-groups.php</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li><a href="#sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampreportsclassesreportclassmeetupeventsphp">sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/classes/report/class-meetup-events.php</a></li>
<li><a href="#sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampreportsviewshtmlmeetupeventsphp">sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/views/html/meetup-events.php</a></li>
<li><a href="#sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampreportsviewsreportmeetupeventsphp">sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/views/report/meetup-events.php</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampreportsclassesreportclassmeetupeventsphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/classes/report/class-meetup-events.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/classes/report/class-meetup-events.php                           (rev 0)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/classes/report/class-meetup-events.php     2018-08-01 00:17:32 UTC (rev 7573)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,551 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/**
+ * Meetup Groups.
+ *
+ * @package WordCamp\Reports
+ */
+
+namespace WordCamp\Reports\Report;
+defined( 'WPINC' ) || die();
+
+use Exception;
+use DateTime, DateInterval;
+use WP_Error;
+use function WordCamp\Reports\get_views_dir_path;
+use function WordCamp\Reports\Validation\validate_date_range;
+use function WordCamp\Reports\Time\{year_array, quarter_array, month_array, convert_time_period_to_date_range};
+use WordCamp\Utilities\{Meetup_Client, Export_CSV};
+
+/**
+ * Class Meetup_Events
+ *
+ * @package WordCamp\Reports\Report
+ */
+class Meetup_Events extends Base {
+       /**
+        * Report name.
+        *
+        * @var string
+        */
+       public static $name = 'Meetup Events';
+
+       /**
+        * Report slug.
+        *
+        * @var string
+        */
+       public static $slug = 'meetup-events';
+
+       /**
+        * Report description.
+        *
+        * @var string
+        */
+       public static $description = 'Details on meetup events during a given time period.';
+
+       /**
+        * Report methodology.
+        *
+        * @var string
+        */
+       public static $methodology = "
+               Retrieve data about events in the Chapter program from the Meetup.com API.
+       ";
+
+       /**
+        * Report group.
+        *
+        * @var string
+        */
+       public static $group = 'meetup';
+
+       /**
+        * Shortcode tag for outputting the public report form.
+        *
+        * todo
+        *
+        * @var string
+        */
+       //public static $shortcode_tag = 'meetup_events_report';
+
+       /**
+        * The date range that defines the scope of the report data.
+        *
+        * @var null|Date_Range
+        */
+       public $range = null;
+
+       /**
+        * Data fields that can be visible in a public context.
+        *
+        * @var array An associative array of key/default value pairs.
+        */
+       protected $public_data_fields = [
+               'id'           => '',
+               'event_url'    => '',
+               'name'         => '',
+               'description'  => '',
+               'time'         => 0,
+               'group'        => '',
+               'city'         => '',
+               'l10n_country' => '',
+               'latitude'     => 0,
+               'longitude'    => 0,
+       ];
+
+       /**
+        * Meetup_Events 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 array  $options          {
+        *     Optional. Additional report parameters.
+        *     See Base::__construct and the functions in WordCamp\Reports\Validation for additional parameters.
+        * }
+        */
+       public function __construct( $start_date, $end_date, array $options = [] ) {
+               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()
+                       );
+               }
+       }
+
+       /**
+        * Generate a cache key.
+        *
+        * @return string
+        */
+       protected function get_cache_key() {
+               $cache_key_segments = [
+                       parent::get_cache_key(),
+                       $this->range->generate_cache_key_segment(),
+               ];
+
+               return implode( '_', $cache_key_segments );
+       }
+
+       /**
+        * Generate a cache expiration interval.
+        *
+        * @return int A time interval in seconds.
+        */
+       protected function get_cache_expiration() {
+               return $this->range->generate_cache_duration( parent::get_cache_expiration() );
+       }
+
+       /**
+        * 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;
+               }
+
+               $meetup = new Meetup_Client();
+
+               $groups = $meetup->get_groups();
+               $group_ids = wp_list_pluck( $groups, 'id' );
+               $groups = array_combine( $group_ids, $groups );
+
+               $events = $meetup->get_events( $group_ids, array(
+                       'status' => 'upcoming,past',
+                       'time'   => sprintf(
+                               '%d,%d',
+                               $this->range->start->getTimestamp() * 1000,
+                               $this->range->end->getTimestamp() * 1000
+                       ),
+               ) );
+
+               $data = [];
+
+               $relevant_keys = array_flip( array( 'id', 'event_url', 'name', 'description', 'time', 'group', 'city', 'l10n_country', 'latitude', 'longitude' ) );
+
+               foreach ( $events as $event ) {
+                       $group_id = $event['group']['id'];
+                       $event    = wp_parse_args( $event, array_fill_keys( $relevant_keys, '' ) );
+
+                       $event['description']  = isset( $event['description'] ) ? trim( $event['description'] ) : '';
+                       $event['time']         = absint( $event['time'] ) / 1000; // Convert to seconds.
+                       $event['group']        = isset( $event['group']['name'] ) ? $event['group']['name'] : $groups[ $group_id ]['name'];
+                       $event['city']         = isset( $event['venue']['city'] ) ? $event['venue']['city'] : $groups[ $group_id ]['city'];
+                       $event['l10n_country'] = isset( $event['venue']['localized_country_name'] ) ? $event['venue']['localized_country_name'] : $groups[ $group_id ]['country'];
+                       $event['latitude']     = ! empty( $event['venue']['lat'] ) ? $event['venue']['lat'] : $groups[ $group_id ]['lat'];
+                       $event['longitude']    = ! empty( $event['venue']['lon'] ) ? $event['venue']['lon'] : $groups[ $group_id ]['lon'];
+
+                       $data[] = array_intersect_key( $event, $relevant_keys );
+               }
+
+               $data = $this->filter_data_fields( $data );
+               $this->maybe_cache_data( $data );
+
+               return $data;
+       }
+
+       /**
+        * Compile the report data into results.
+        *
+        * @param array $data The data to compile.
+        *
+        * @return array
+        */
+       public function compile_report_data( array $data ) {
+               $compiled_data = [
+                       'total_events'              => count( $data ),
+                       'total_events_by_country'   => [],
+                       'total_events_by_group'     => [],
+                       'monthly_events'            => [],
+                       'monthly_events_by_country' => [],
+                       'monthly_events_by_group'   => [],
+               ];
+
+               try {
+                       $compiled_data['monthly_events'] = $this->count_events_by_month( $data );
+               } catch ( Exception $e ) {
+                       $this->error->add(
+                               self::$slug . '-compilation-error',
+                               $e->getMessage()
+                       );
+
+                       return $compiled_data;
+               }
+
+               try {
+                       $events_by_country = $this->sort_events_by_field( 'l10n_country', $data );
+               } catch ( Exception $e ) {
+                       $this->error->add(
+                               self::$slug . '-compilation-error',
+                               $e->getMessage()
+                       );
+
+                       return $compiled_data;
+               }
+
+               uasort( $events_by_country, function( $a, $b ) {
+                       $count_a = count( $a );
+                       $count_b = count( $b );
+
+                       if ( $count_a === $count_b ) {
+                               return 0;
+                       }
+
+                       return ( $count_a < $count_b ) ? 1 : -1;
+               } );
+
+               foreach ( $events_by_country as $country => $events ) {
+                       $compiled_data['total_events_by_country'][ $country ] = count( $events );
+
+                       try {
+                               $compiled_data['monthly_events_by_country'][ $country ] = $this->count_events_by_month( $events );
+                       } catch ( Exception $e ) {
+                               $this->error->add(
+                                       self::$slug . '-compilation-error',
+                                       $e->getMessage()
+                               );
+
+                               return $compiled_data;
+                       }
+               }
+
+               try {
+                       $events_by_group = $this->sort_events_by_field( 'group', $data );
+               } catch ( Exception $e ) {
+                       $this->error->add(
+                               self::$slug . '-compilation-error',
+                               $e->getMessage()
+                       );
+
+                       return $compiled_data;
+               }
+
+               uasort( $events_by_group, function( $a, $b ) {
+                       $count_a = count( $a );
+                       $count_b = count( $b );
+
+                       if ( $count_a === $count_b ) {
+                               return 0;
+                       }
+
+                       return ( $count_a < $count_b ) ? 1 : -1;
+               } );
+
+               foreach ( $events_by_group as $group => $events ) {
+                       $compiled_data['total_events_by_group'][ $group ] = count( $events );
+
+                       try {
+                               $compiled_data['monthly_events_by_group'][ $group ] = $this->count_events_by_month( $events );
+                       } catch ( Exception $e ) {
+                               $this->error->add(
+                                       self::$slug . '-compilation-error',
+                                       $e->getMessage()
+                               );
+
+                               return $compiled_data;
+                       }
+               }
+
+               $compiled_data['groups_with_events'] = count( $compiled_data['total_events_by_group'] );
+
+               $meetup = new Meetup_Client();
+               $total_groups = absint( $meetup->get_result_count( 'pro/wordpress/groups' ) );
+
+               $compiled_data['groups_with_no_events'] = $total_groups - $compiled_data['groups_with_events'];
+
+               return $compiled_data;
+       }
+
+       /**
+        *
+        *
+        * @param string $field
+        * @param array  $data
+        *
+        * @return array
+        * @throws Exception
+        */
+       protected function sort_events_by_field( $field, array $data ) {
+               if ( ! array_key_exists( $field, $data[0] ) ) {
+                       throw new Exception( sprintf(
+                               'Cannot sort events by %s.',
+                               esc_html( $field )
+                       ) );
+               }
+
+               return array_reduce( $data, function( $carry, $item ) use ( $field ) {
+                       $group = $item[ $field ];
+
+                       if ( ! isset( $carry[ $group ] ) ) {
+                               $carry[ $group ] = [];
+                       }
+
+                       $carry[ $group ][] = $item;
+
+                       return $carry;
+               }, [] );
+       }
+
+       /**
+        *
+        *
+        * @param array $events
+        *
+        * @return array
+        * @throws Exception
+        */
+       protected function count_events_by_month( array $events ) {
+               $month_iterator = new DateTime( $this->range->start->format( 'Y-m' ) . '-01' );
+               $end_month      = new DateTime( $this->range->end->format( 'Y-m' ) . '-01' );
+               $interval       = new DateInterval( 'P1M' );
+               $months         = [];
+
+               while ( $month_iterator <= $end_month ) {
+                       $months[ $month_iterator->format( 'M Y' ) ] = 0;
+                       $month_iterator->add( $interval );
+               }
+
+               if ( count( $months ) < 2 ) {
+                       return [];
+               }
+
+               foreach ( $events as $event ) {
+                       $event_date = new DateTime();
+                       $event_date->setTimestamp( $event['time'] );
+
+                       $event_month = $event_date->format( 'M Y' );
+
+                       $months[ $event_month ] ++;
+               }
+
+               return $months;
+       }
+
+       /**
+        * Render an HTML version of the report output.
+        *
+        * @return void
+        */
+       public function render_html() {
+               $data       = $this->compile_report_data( $this->get_data() );
+               $start_date = $this->range->start;
+               $end_date   = $this->range->end;
+
+               if ( ! empty( $this->error->get_error_messages() ) ) {
+                       $this->render_error_html();
+               } else {
+                       include get_views_dir_path() . 'html/meetup-events.php';
+               }
+       }
+
+       /**
+        * Render the page for this report in the WP Admin.
+        *
+        * @return void
+        */
+       public static function render_admin_page() {
+               $start_date = filter_input( INPUT_POST, 'start-date' );
+               $end_date   = filter_input( INPUT_POST, 'end-date' );
+               $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 ( 'Show results' === $action
+                    && wp_verify_nonce( $nonce, 'run-report' )
+                    && current_user_can( 'manage_network' )
+               ) {
+                       $options = array(
+                               'earliest_start' => new DateTime( '2015-01-01' ), // Chapter program started in 2015.
+                       );
+
+                       if ( $refresh ) {
+                               $options['flush_cache'] = true;
+                       }
+
+                       $report = new self( $start_date, $end_date, $options );
+               }
+
+               include get_views_dir_path() . 'report/meetup-events.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' );
+               $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(
+                               'earliest_start' => new DateTime( '2015-01-01' ), // Chapter program started in 2015.
+                       );
+
+                       if ( $refresh ) {
+                               $options['flush_cache'] = true;
+                       }
+
+                       $report = new self( $start_date, $end_date, $options );
+
+                       $filename   = array( $report::$name );
+                       $filename[] = $report->range->start->format( 'Y-m-d' );
+                       $filename[] = $report->range->end->format( 'Y-m-d' );
+
+                       $headers = [ 'Event ID', 'Event URL', 'Event Name', 'Description', 'Date', 'Group Name', 'City', 'Country', 'Country (localized)', 'Latitude', 'Longitude' ];
+
+                       $data = $report->get_data();
+
+                       array_walk( $data, function( &$event ) {
+                               $date = new DateTime();
+                               $date->setTimestamp( $event['time'] );
+                               $event['time'] = $date->format( 'Y-m-d' );
+                       } );
+
+                       $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().
+       }
+
+       /**
+        * Determine whether to render the public report form.
+        *
+        * This shortcode is limited to use on pages.
+        *
+        * @return string HTML content to display shortcode.
+        */
+       public static function handle_shortcode() {
+               $html = '';
+
+               if ( 'page' === get_post_type() ) {
+                       ob_start();
+                       self::render_public_page();
+                       $html = ob_get_clean();
+               }
+
+               return $html;
+       }
+
+       /**
+        * Render the page for this report on the front end.
+        *
+        * @return void
+        */
+       public static function render_public_page() {
+               // Apparently 'year' is a reserved URL parameter on the front end, so we prepend 'report-'.
+               $year   = filter_input( INPUT_GET, 'report-year', FILTER_VALIDATE_INT );
+               $period = filter_input( INPUT_GET, 'period' );
+               $action = filter_input( INPUT_GET, 'action' );
+
+               $years    = year_array( absint( date( 'Y' ) ), 2015 );
+               $quarters = quarter_array();
+               $months   = month_array();
+
+               if ( ! $year ) {
+                       $year = absint( date( 'Y' ) );
+               }
+
+               if ( ! $period ) {
+                       $period = absint( date( 'm' ) );
+               }
+
+               $report = null;
+
+               if ( 'Show results' === $action ) {
+                       $error = null;
+
+                       try {
+                               $range = convert_time_period_to_date_range( $year, $period );
+                       } catch ( Exception $e ) {
+                               $error = new WP_Error(
+                                       self::$slug . '-time-period-error',
+                                       $e->getMessage()
+                               );
+                       }
+
+                       $options = array(
+                               'earliest_start' => new DateTime( '2015-01-01' ), // Chapter program started in 2015.
+                       );
+
+                       $report = new self( $range->start, $range->end, $options );
+
+                       if ( ! is_null( $error ) ) {
+                               $report->merge_errors( $error, $report->error );
+                       }
+               }
+
+               include get_views_dir_path() . 'public/meetup-events.php';
+       }
+}
</ins></span></pre></div>
<a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampreportsclassesreportclassmeetupgroupsphp"></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-meetup-groups.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/classes/report/class-meetup-groups.php   2018-07-31 23:57:03 UTC (rev 7572)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/classes/report/class-meetup-groups.php     2018-08-01 00:17:32 UTC (rev 7573)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -8,8 +8,14 @@
</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 DateTimeImmutable, DateTime;
+use WP_Error;
</ins><span class="cx" style="display: block; padding: 0 10px"> use WordCamp\Reports;
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-use WordCamp\Utilities;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+use function WordCamp\Reports\get_views_dir_path;
+use function WordCamp\Reports\Validation\validate_date_range;
+use function WordCamp\Reports\Time\{year_array, quarter_array, month_array, convert_time_period_to_date_range};
+use WordCamp\Utilities\{Meetup_Client, Export_CSV};
</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 Meetup_Groups
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -16,7 +22,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">  *
</span><span class="cx" style="display: block; padding: 0 10px">  * @package WordCamp\Reports\Report
</span><span class="cx" style="display: block; padding: 0 10px">  */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-class Meetup_Groups extends Date_Range {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+class Meetup_Groups extends Base {
</ins><span class="cx" style="display: block; padding: 0 10px">         /**
</span><span class="cx" style="display: block; padding: 0 10px">         * Report name.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -62,6 +68,13 @@
</span><span class="cx" style="display: block; padding: 0 10px">        public static $shortcode_tag = 'meetup_groups_report';
</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">+         * The date range that defines the scope of the report data.
+        *
+        * @var null|Date_Range
+        */
+       public $range = null;
+
+       /**
</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">@@ -80,6 +93,52 @@
</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">+         * Meetup_Groups 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 array  $options          {
+        *     Optional. Additional report parameters.
+        *     See Base::__construct and the functions in WordCamp\Reports\Validation for additional parameters.
+        * }
+        */
+       public function __construct( $start_date, $end_date, array $options = [] ) {
+               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()
+                       );
+               }
+       }
+
+       /**
+        * Generate a cache key.
+        *
+        * @return string
+        */
+       protected function get_cache_key() {
+               $cache_key_segments = [
+                       parent::get_cache_key(),
+                       $this->range->generate_cache_key_segment(),
+               ];
+
+               return implode( '_', $cache_key_segments );
+       }
+
+       /**
+        * Generate a cache expiration interval.
+        *
+        * @return int A time interval in seconds.
+        */
+       protected function get_cache_expiration() {
+               return $this->range->generate_cache_duration( parent::get_cache_expiration() );
+       }
+
+       /**
</ins><span class="cx" style="display: block; padding: 0 10px">          * Query and parse the data for the report.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @return array
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -96,10 +155,10 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        return $data;
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $meetup = new Utilities\Meetup_Client();
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $meetup = new Meetup_Client();
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $data = $meetup->get_groups( array(
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        'pro_join_date_max' => $this->end_date->getTimestamp() * 1000, // Meetup API uses milliseconds.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 'pro_join_date_max' => $this->range->end->getTimestamp() * 1000, // Meetup API uses milliseconds.
</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 ( is_wp_error( $data ) ) {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -123,10 +182,10 @@
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="cx" style="display: block; padding: 0 10px">        public function compile_report_data( array $data ) {
</span><span class="cx" style="display: block; padding: 0 10px">                $joined_groups = array_filter( $data, function( $group ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        $join_date = new \DateTime();
-                       $join_date->setTimestamp( intval( $group['pro_join_date'] / 1000 ) ); // Meetup API uses milliseconds.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 $join_date = new DateTimeImmutable();
+                       $join_date = $join_date->setTimestamp( intval( $group['pro_join_date'] / 1000 ) ); // Meetup API uses milliseconds.
</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 ( $join_date >= $this->start_date && $join_date <= $this->end_date ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 if ( $join_date >= $this->range->start && $join_date <= $this->range->end ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                                 return true;
</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">@@ -219,13 +278,13 @@
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="cx" style="display: block; padding: 0 10px">        public function render_html() {
</span><span class="cx" style="display: block; padding: 0 10px">                $data       = $this->compile_report_data( $this->get_data() );
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $start_date = $this->start_date;
-               $end_date   = $this->end_date;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $start_date = $this->range->start;
+               $end_date   = $this->range->end;
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                if ( ! empty( $this->error->get_error_messages() ) ) {
</span><span class="cx" style="display: block; padding: 0 10px">                        $this->render_error_html();
</span><span class="cx" style="display: block; padding: 0 10px">                } else {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        include Reports\get_views_dir_path() . 'html/meetup-groups.php';
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 include get_views_dir_path() . 'html/meetup-groups.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">@@ -235,11 +294,11 @@
</span><span class="cx" style="display: block; padding: 0 10px">         * @return void
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="cx" style="display: block; padding: 0 10px">        public static function render_admin_page() {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $start_date  = filter_input( INPUT_POST, 'start-date' );
-               $end_date    = filter_input( INPUT_POST, 'end-date' );
-               $refresh     = filter_input( INPUT_POST, 'refresh', FILTER_VALIDATE_BOOLEAN );
-               $action      = filter_input( INPUT_POST, 'action' );
-               $nonce       = filter_input( INPUT_POST, self::$slug . '-nonce' );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $start_date = filter_input( INPUT_POST, 'start-date' );
+               $end_date   = filter_input( INPUT_POST, 'end-date' );
+               $refresh    = filter_input( INPUT_POST, 'refresh', FILTER_VALIDATE_BOOLEAN );
+               $action     = filter_input( INPUT_POST, 'action' );
+               $nonce      = filter_input( INPUT_POST, self::$slug . '-nonce' );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $report = null;
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -248,7 +307,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                     && current_user_can( 'manage_network' )
</span><span class="cx" style="display: block; padding: 0 10px">                ) {
</span><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">-                                'earliest_start' => new \DateTime( '2015-01-01' ), // Chapter program started in 2015.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         'earliest_start' => new DateTime( '2015-01-01' ), // Chapter program started in 2015.
</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 ( $refresh ) {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -256,14 +315,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">                        $report = new self( $start_date, $end_date, $options );
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-
-                       // The report adjusts the end date in some circumstances.
-                       if ( empty( $report->error->get_error_messages() ) ) {
-                               $end_date = $report->end_date->format( 'Y-m-d' );
-                       }
</del><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">-                include Reports\get_views_dir_path() . 'report/meetup-groups.php';
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         include get_views_dir_path() . 'report/meetup-groups.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">@@ -272,11 +326,11 @@
</span><span class="cx" style="display: block; padding: 0 10px">         * @return void
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="cx" style="display: block; padding: 0 10px">        public static function export_to_file() {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $start_date  = filter_input( INPUT_POST, 'start-date' );
-               $end_date    = filter_input( INPUT_POST, 'end-date' );
-               $refresh     = filter_input( INPUT_POST, 'refresh', FILTER_VALIDATE_BOOLEAN );
-               $action      = filter_input( INPUT_POST, 'action' );
-               $nonce       = filter_input( INPUT_POST, self::$slug . '-nonce' );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $start_date = filter_input( INPUT_POST, 'start-date' );
+               $end_date   = filter_input( INPUT_POST, 'end-date' );
+               $refresh    = filter_input( INPUT_POST, 'refresh', FILTER_VALIDATE_BOOLEAN );
+               $action     = filter_input( INPUT_POST, 'action' );
+               $nonce      = filter_input( INPUT_POST, self::$slug . '-nonce' );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $report = null;
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -286,7 +340,7 @@
</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><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">-                                'earliest_start' => new \DateTime( '2015-01-01' ), // Chapter program started in 2015.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         'earliest_start' => new DateTime( '2015-01-01' ), // Chapter program started in 2015.
</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 ( $refresh ) {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -295,14 +349,9 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                        $report = new self( $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">-                        // The report adjusts the end date in some circumstances.
-                       if ( empty( $report->error->get_error_messages() ) ) {
-                               $end_date = $report->end_date->format( 'Y-m-d' );
-                       }
-
</del><span class="cx" style="display: block; padding: 0 10px">                         $filename   = array( $report::$name );
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        $filename[] = $report->start_date->format( 'Y-m-d' );
-                       $filename[] = $report->end_date->format( 'Y-m-d' );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 $filename[] = $report->range->start->format( 'Y-m-d' );
+                       $filename[] = $report->range->end->format( 'Y-m-d' );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                        $headers = array( 'Name', 'URL', 'City', 'State', 'Country', 'Latitude', 'Longitude', 'Member Count', 'Date Founded', 'Date Joined' );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -314,7 +363,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                $group['pro_join_date'] = ( $group['pro_join_date'] ) ? date( 'Y-m-d', $group['pro_join_date'] / 1000 ) : '';
</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">-                        $exporter = new Utilities\Export_CSV( array(
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 $exporter = new Export_CSV( array(
</ins><span class="cx" style="display: block; padding: 0 10px">                                 'filename' => $filename,
</span><span class="cx" style="display: block; padding: 0 10px">                                'headers'  => $headers,
</span><span class="cx" style="display: block; padding: 0 10px">                                'data'     => $data,
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -358,9 +407,9 @@
</span><span class="cx" style="display: block; padding: 0 10px">                $period = filter_input( INPUT_GET, 'period' );
</span><span class="cx" style="display: block; padding: 0 10px">                $action = filter_input( INPUT_GET, 'action' );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $years    = self::year_array( absint( date( 'Y' ) ), 2015 );
-               $quarters = self::quarter_array();
-               $months   = self::month_array();
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $years    = year_array( absint( date( 'Y' ) ), 2015 );
+               $quarters = quarter_array();
+               $months   = month_array();
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                if ( ! $year ) {
</span><span class="cx" style="display: block; padding: 0 10px">                        $year = absint( date( 'Y' ) );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -373,15 +422,28 @@
</span><span class="cx" style="display: block; padding: 0 10px">                $report = null;
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                if ( 'Show results' === $action ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        $range = self::convert_time_period_to_date_range( $year, $period );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 $error = null;
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                        try {
+                               $range = convert_time_period_to_date_range( $year, $period );
+                       } catch ( Exception $e ) {
+                               $error = new WP_Error(
+                                       self::$slug . '-time-period-error',
+                                       $e->getMessage()
+                               );
+                       }
+
</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">-                                'earliest_start' => new \DateTime( '2015-01-01' ), // Chapter program started in 2015.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         'earliest_start' => new DateTime( '2015-01-01' ), // Chapter program started in 2015.
</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">-                        $report = new self( $range['start_date'], $range['end_date'], $options );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 $report = new self( $range->start, $range->end, $options );
+
+                       if ( ! is_null( $error ) ) {
+                               $report->merge_errors( $error, $report->error );
+                       }
</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">-                include Reports\get_views_dir_path() . 'public/meetup-groups.php';
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         include get_views_dir_path() . 'public/meetup-groups.php';
</ins><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_htmlwpcontentpluginswordcampreportsincludesvalidationphp"></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/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  2018-07-31 23:57:03 UTC (rev 7572)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/includes/validation.php    2018-08-01 00:17:32 UTC (rev 7573)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -42,15 +42,21 @@
</span><span class="cx" style="display: block; padding: 0 10px">                throw new Exception( 'Please enter valid start and end dates.' );
</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">-        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()
-               ) );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ if ( $start_date instanceof DateTime ) {
+               $start_date = DateTimeImmutable::createFromMutable( $start_date );
</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">+        if ( ! $start_date instanceof DateTimeImmutable ) {
+               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()
+                       ) );
+               }
+       }
+
</ins><span class="cx" style="display: block; padding: 0 10px">         // No future start dates.
</span><span class="cx" style="display: block; padding: 0 10px">        if ( ! $config['allow_future_start'] && $start_date > date_create( 'now' ) ) {
</span><span class="cx" style="display: block; padding: 0 10px">                throw new Exception( 'Please enter a start date that is the same as or before today\'s date.' );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -64,15 +70,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><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        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()
-               ) );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ if ( $end_date instanceof DateTime ) {
+               $end_date = DateTimeImmutable::createFromMutable( $end_date );
</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">+        if ( ! $end_date instanceof DateTimeImmutable ) {
+               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()
+                       ) );
+               }
+       }
+
</ins><span class="cx" style="display: block; padding: 0 10px">         // No negative date intervals.
</span><span class="cx" style="display: block; padding: 0 10px">        if ( $start_date > $end_date ) {
</span><span class="cx" style="display: block; padding: 0 10px">                throw new Exception( 'Please enter an end date that is the same as or after the start date.' );
</span></span></pre></div>
<a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampreportsindexphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/index.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/index.php        2018-07-31 23:57:03 UTC (rev 7572)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/index.php  2018-08-01 00:17:32 UTC (rev 7573)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -136,6 +136,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                __NAMESPACE__ . '\Report\WordCamp_Details',
</span><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><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                __NAMESPACE__ . '\Report\Meetup_Events',
</ins><span class="cx" style="display: block; padding: 0 10px">                 __NAMESPACE__ . '\Report\WordCamp_Payment_Methods',
</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_htmlwpcontentpluginswordcampreportsviewshtmlmeetupeventsphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/views/html/meetup-events.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/views/html/meetup-events.php                             (rev 0)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/views/html/meetup-events.php       2018-08-01 00:17:32 UTC (rev 7573)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,100 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/**
+ * @package WordCamp\Reports
+ */
+
+namespace WordCamp\Reports\Views\HTML\Meetup_Events;
+defined( 'WPINC' ) || die();
+
+/** @var \DateTime $start_date */
+/** @var \DateTime $end_date */
+/** @var array $data */
+?>
+
+<?php if ( $data['total_events'] ) : ?>
+       <h3>Total meetup events between <?php echo esc_html( $start_date->format( 'M jS, Y' ) ); ?> and <?php echo esc_html( $end_date->format( 'M jS, Y' ) ); ?></h3>
+
+       <h4>By country</h4>
+
+       <table class="striped widefat but-not-too-wide">
+               <thead>
+               <tr>
+                       <td>Country</td>
+                       <?php foreach ( array_keys( $data['monthly_events'] ) as $month ) : ?>
+                               <td><?php echo esc_html( $month ); ?></td>
+                       <?php endforeach; ?>
+                       <td>Total</td>
+               </tr>
+               </thead>
+               <tbody>
+               <?php foreach ( $data['monthly_events_by_country'] as $country => $month_counts ) : ?>
+                       <tr>
+                               <td><?php echo esc_html( $country ); ?></td>
+                               <?php foreach ( $month_counts as $count ) : ?>
+                                       <td class="number"><?php echo number_format_i18n( $count ); ?></td>
+                               <?php endforeach; ?>
+                               <td class="number total"><?php echo number_format_i18n( $data['total_events_by_country'][ $country ] ); ?></td>
+                       </tr>
+               <?php endforeach; ?>
+               <tr>
+                       <td class="total">Total</td>
+                       <?php foreach ( $data['monthly_events'] as $count ) : ?>
+                               <td class="number total"><?php echo number_format_i18n( $count ); ?></td>
+                       <?php endforeach; ?>
+                       <td class="number total"><?php echo number_format_i18n( $data['total_events'] ); ?></td>
+               </tr>
+               </tbody>
+       </table>
+
+       <h4>By group</h4>
+
+       <table class="striped widefat but-not-too-wide">
+               <tr>
+                       <td>Groups with at least one event during the date range</td>
+                       <td class="number"><?php echo number_format_i18n( $data['groups_with_events'] ); ?></td>
+               </tr>
+               <tr>
+                       <td>Groups with no events during the date range</td>
+                       <td class="number"><?php echo number_format_i18n( $data['groups_with_no_events'] ); ?></td>
+               </tr>
+       </table>
+
+       <table class="striped widefat but-not-too-wide">
+               <thead>
+               <tr>
+                       <td>Group</td>
+                       <?php foreach ( array_keys( $data['monthly_events'] ) as $month ) : ?>
+                               <td><?php echo esc_html( $month ); ?></td>
+                       <?php endforeach; ?>
+                       <td>Total</td>
+               </tr>
+               </thead>
+               <tbody>
+               <?php foreach ( $data['monthly_events_by_group'] as $group => $month_counts ) : ?>
+                       <tr>
+                               <td><?php echo esc_html( $group ); ?></td>
+                               <?php foreach ( $month_counts as $count ) : ?>
+                                       <td class="number"><?php echo number_format_i18n( $count ); ?></td>
+                               <?php endforeach; ?>
+                               <td class="number total"><?php echo number_format_i18n( $data['total_events_by_group'][ $group ] ); ?></td>
+                       </tr>
+               <?php endforeach; ?>
+               <tr>
+                       <td class="total">Total</td>
+                       <?php foreach ( $data['monthly_events'] as $count ) : ?>
+                               <td class="number total"><?php echo number_format_i18n( $count ); ?></td>
+                       <?php endforeach; ?>
+                       <td class="number total"><?php echo number_format_i18n( $data['total_events'] ); ?></td>
+               </tr>
+               </tbody>
+       </table>
+<?php else : ?>
+       <p>
+               No data
+               <?php if ( $start_date->format( 'Y-m-d' ) === $end_date->format( 'Y-m-d' ) ) : ?>
+                       on <?php echo esc_html( $start_date->format( 'M jS, Y' ) ); ?>
+               <?php else : ?>
+                       between <?php echo esc_html( $start_date->format( 'M jS, Y' ) ); ?> and <?php echo esc_html( $end_date->format( 'M jS, Y' ) ); ?>
+               <?php endif; ?>
+       </p>
+<?php endif; ?>
</ins></span></pre></div>
<a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampreportsviewshtmlmeetupgroupsphp"></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/html/meetup-groups.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/views/html/meetup-groups.php     2018-07-31 23:57:03 UTC (rev 7572)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/views/html/meetup-groups.php       2018-08-01 00:17:32 UTC (rev 7573)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -3,7 +3,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">  * @package WordCamp\Reports
</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">-namespace WordCamp\Reports\Views\HTML\Ticket_Revenue;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+namespace WordCamp\Reports\Views\HTML\Meetup_Groups;
</ins><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"> /** @var \DateTime $start_date */
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -12,16 +12,14 @@
</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"> <?php if ( $data['total_groups'] ) : ?>
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        <h3>Meetup groups in the chapter program as of <?php echo esc_html( $end_date->format( 'M jS, Y' ) ); ?></h3>
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ <h3>Total meetup groups in the chapter program as of <?php echo esc_html( $end_date->format( 'M jS, Y' ) ); ?></h3>
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        <h4>Total groups: <?php echo number_format_i18n( $data['total_groups'] ); ?></h4>
-       <h4>Total groups by country:</h4>
-
</del><span class="cx" style="display: block; padding: 0 10px">         <table class="striped widefat but-not-too-wide">
</span><span class="cx" style="display: block; padding: 0 10px">                <thead>
</span><span class="cx" style="display: block; padding: 0 10px">                <tr>
</span><span class="cx" style="display: block; padding: 0 10px">                        <td>Country</td>
</span><span class="cx" style="display: block; padding: 0 10px">                        <td># of Groups</td>
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                        <td># of Members (non-unique)</td>
</ins><span class="cx" style="display: block; padding: 0 10px">                 </tr>
</span><span class="cx" style="display: block; padding: 0 10px">                </thead>
</span><span class="cx" style="display: block; padding: 0 10px">                <tbody>
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -29,42 +27,26 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        <tr>
</span><span class="cx" style="display: block; padding: 0 10px">                                <td><?php echo esc_html( $country ); ?></td>
</span><span class="cx" style="display: block; padding: 0 10px">                                <td class="number"><?php echo number_format_i18n( $data['total_groups_by_country'][ $country ] ); ?></td>
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                <td class="number"><?php echo number_format_i18n( $data['total_members_by_country'][ $country ] ); ?></td>
</ins><span class="cx" style="display: block; padding: 0 10px">                         </tr>
</span><span class="cx" style="display: block; padding: 0 10px">                <?php endforeach; ?>
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                </tbody>
-       </table>
-
-       <h4>Total group members (non-unique): <?php echo number_format_i18n( $data['total_members'] ); ?></h4>
-       <h4>Total group members by country:</h4>
-
-       <table class="striped widefat but-not-too-wide">
-               <thead>
</del><span class="cx" style="display: block; padding: 0 10px">                 <tr>
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        <td>Country</td>
-                       <td># of Members</td>
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 <td class="total">Total</td>
+                       <td class="number total"><?php echo number_format_i18n( $data['total_groups'] ); ?></td>
+                       <td class="number total"><?php echo number_format_i18n( $data['total_members'] ); ?></td>
</ins><span class="cx" style="display: block; padding: 0 10px">                 </tr>
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                </thead>
-               <tbody>
-               <?php foreach ( array_keys( $data['total_members_by_country'] ) as $country ) : ?>
-                       <tr>
-                               <td><?php echo esc_html( $country ); ?></td>
-                               <td class="number"><?php echo number_format_i18n( $data['total_members_by_country'][ $country ] ); ?></td>
-                       </tr>
-               <?php endforeach; ?>
</del><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><span class="cx" style="display: block; padding: 0 10px">        <?php if ( $data['joined_groups'] ) : ?>
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                <h3>Meetup groups that joined the chapter program between <?php echo esc_html( $start_date->format( 'M jS, Y' ) ); ?> and <?php echo esc_html( $end_date->format( 'M jS, Y' ) ); ?></h3>
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         <h3>New meetup groups that joined the chapter program between <?php echo esc_html( $start_date->format( 'M jS, Y' ) ); ?> and <?php echo esc_html( $end_date->format( 'M jS, Y' ) ); ?></h3>
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                <h4>Total groups that joined: <?php echo number_format_i18n( $data['joined_groups'] ); ?></h4>
-               <h4>Total groups that joined by country:</h4>
-
</del><span class="cx" style="display: block; padding: 0 10px">                 <table class="striped widefat but-not-too-wide">
</span><span class="cx" style="display: block; padding: 0 10px">                        <thead>
</span><span class="cx" style="display: block; padding: 0 10px">                        <tr>
</span><span class="cx" style="display: block; padding: 0 10px">                                <td>Country</td>
</span><span class="cx" style="display: block; padding: 0 10px">                                <td># of Groups</td>
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                <td># of Members (non-unique)</td>
</ins><span class="cx" style="display: block; padding: 0 10px">                         </tr>
</span><span class="cx" style="display: block; padding: 0 10px">                        </thead>
</span><span class="cx" style="display: block; padding: 0 10px">                        <tbody>
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -72,28 +54,14 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                <tr>
</span><span class="cx" style="display: block; padding: 0 10px">                                        <td><?php echo esc_html( $country ); ?></td>
</span><span class="cx" style="display: block; padding: 0 10px">                                        <td class="number"><?php echo number_format_i18n( $data['joined_groups_by_country'][ $country ] ); ?></td>
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                        <td class="number"><?php echo number_format_i18n( $data['joined_members_by_country'][ $country ] ); ?></td>
</ins><span class="cx" style="display: block; padding: 0 10px">                                 </tr>
</span><span class="cx" style="display: block; padding: 0 10px">                        <?php endforeach; ?>
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        </tbody>
-               </table>
-
-               <h4>Total group members that joined (non-unique): <?php echo number_format_i18n( $data['joined_members'] ); ?></h4>
-               <h4>Total group members that joined by country:</h4>
-
-               <table class="striped widefat but-not-too-wide">
-                       <thead>
</del><span class="cx" style="display: block; padding: 0 10px">                         <tr>
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                <td>Country</td>
-                               <td># of Members</td>
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         <td class="total">Total</td>
+                               <td class="number total"><?php echo number_format_i18n( $data['joined_groups'] ); ?></td>
+                               <td class="number total"><?php echo number_format_i18n( $data['joined_members'] ); ?></td>
</ins><span class="cx" style="display: block; padding: 0 10px">                         </tr>
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        </thead>
-                       <tbody>
-                       <?php foreach ( array_keys( $data['joined_members_by_country'] ) as $country ) : ?>
-                               <tr>
-                                       <td><?php echo esc_html( $country ); ?></td>
-                                       <td class="number"><?php echo number_format_i18n( $data['joined_members_by_country'][ $country ] ); ?></td>
-                               </tr>
-                       <?php endforeach; ?>
</del><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">        <?php endif; ?>
</span></span></pre></div>
<a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampreportsviewsreportmeetupeventsphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/views/report/meetup-events.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/views/report/meetup-events.php                           (rev 0)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/views/report/meetup-events.php     2018-08-01 00:17:32 UTC (rev 7573)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,59 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/**
+ * @package WordCamp\Reports
+ */
+
+namespace WordCamp\Reports\Views\Report\Meetup_Events;
+defined( 'WPINC' ) || die();
+
+use WordCamp\Reports;
+use WordCamp\Reports\Report;
+
+/** @var string $start_date */
+/** @var string $end_date */
+/** @var Report\Meetup_Events|null $report */
+?>
+
+<div class="wrap">
+       <h1>
+               <a href="<?php echo esc_attr( Reports\get_page_url() ); ?>">WordCamp Reports</a>
+               &raquo;
+               <?php echo esc_html( Report\Meetup_Events::$name ); ?>
+       </h1>
+
+       <?php echo wpautop( wp_kses_post( Report\Meetup_Events::$description ) ); ?>
+
+       <h4>Methodology</h4>
+
+       <?php echo wpautop( wp_kses_post( Report\Meetup_Events::$methodology ) ); ?>
+
+       <form method="post" action="">
+               <?php wp_nonce_field( 'run-report', Report\Meetup_Events::$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 ) ?>" /></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 ) ?>" /></td>
+                       </tr>
+                       <tr>
+                               <th scope="row"><label for="refresh">Refresh results</label></th>
+                               <td><input type="checkbox" id="refresh" name="refresh" /></td>
+                       </tr>
+                       </tbody>
+               </table>
+
+               <?php submit_button( 'Show results', 'primary', 'action', false ); ?>
+               <?php submit_button( 'Export CSV', 'secondary', 'action', false ); ?>
+       </form>
+
+       <?php if ( $report instanceof Report\Meetup_Events ) : ?>
+               <div class="report-results">
+                       <?php $report->render_html(); ?>
+               </div>
+       <?php endif; ?>
+</div>
</ins></span></pre>
</div>
</div>

</body>
</html>