<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[7516] sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports: WordCamp Reports: Add WordCamp Details report</title>
</head>
<body>
<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; }
#msg dl a { font-weight: bold}
#msg dl a:link { color:#fc3; }
#msg dl a:active { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { white-space: pre-line; overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta" style="font-size: 105%">
<dt style="float: left; width: 6em; font-weight: bold">Revision</dt> <dd><a style="font-weight: bold" href="http://meta.trac.wordpress.org/changeset/7516">7516</a><script type="application/ld+json">{"@context":"http://schema.org","@type":"EmailMessage","description":"Review this Commit","action":{"@type":"ViewAction","url":"http://meta.trac.wordpress.org/changeset/7516","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>coreymckrill</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2018-07-28 00:46:28 +0000 (Sat, 28 Jul 2018)</dd>
</dl>
<pre style='padding-left: 1em; margin: 2em 0; border-left: 2px solid #ccc; line-height: 1.25; font-size: 105%; font-family: sans-serif'>WordCamp Reports: Add WordCamp Details report
This is the first iteration of a report that allows WPCS staff to download a
spreadsheet of WordCamps and related data.</pre>
<h3>Modified Paths</h3>
<ul>
<li><a href="#sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampreportsclassesreportclassbasephp">sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/classes/report/class-base.php</a></li>
<li><a href="#sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampreportsclassesreportclasswordcampstatusphp">sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/classes/report/class-wordcamp-status.php</a></li>
<li><a href="#sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampreportsindexphp">sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/index.php</a></li>
</ul>
<h3>Added Paths</h3>
<ul>
<li><a href="#sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampreportsclassesreportclasswordcampdetailsphp">sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/classes/report/class-wordcamp-details.php</a></li>
<li>sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/classes/utility/</li>
<li><a href="#sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampreportsclassesutilityclassdaterangephp">sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/classes/utility/class-date-range.php</a></li>
<li>sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/includes/</li>
<li><a href="#sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampreportsincludestimephp">sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/includes/time.php</a></li>
<li><a href="#sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampreportsincludesvalidationphp">sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/includes/validation.php</a></li>
<li><a href="#sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampreportsviewsreportwordcampdetailsphp">sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/views/report/wordcamp-details.php</a></li>
</ul>
</div>
<div id="patch">
<h3>Diff</h3>
<a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampreportsclassesreportclassbasephp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/classes/report/class-base.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/classes/report/class-base.php 2018-07-27 17:02:38 UTC (rev 7515)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/classes/report/class-base.php 2018-07-28 00:46:28 UTC (rev 7516)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -134,6 +134,21 @@
</span><span class="cx" style="display: block; padding: 0 10px"> public abstract function compile_report_data( array $data );
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * Determine the data fields safelist based on the context of the report.
+ *
+ * @return array The list of fields that are safe to include.
+ */
+ protected function get_data_fields_safelist() {
+ $safelist = $this->public_data_fields;
+
+ if ( false === $this->options['public'] ) {
+ $safelist = array_merge( $safelist, $this->private_data_fields );
+ }
+
+ return $safelist;
+ }
+
+ /**
</ins><span class="cx" style="display: block; padding: 0 10px"> * Filter the report data prior to caching and compiling.
</span><span class="cx" style="display: block; padding: 0 10px"> *
</span><span class="cx" style="display: block; padding: 0 10px"> * @param array $data The data to filter.
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -141,12 +156,8 @@
</span><span class="cx" style="display: block; padding: 0 10px"> * @return array
</span><span class="cx" style="display: block; padding: 0 10px"> */
</span><span class="cx" style="display: block; padding: 0 10px"> protected function filter_data_fields( array $data ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- $safelist = $this->public_data_fields;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ $safelist = $this->get_data_fields_safelist();
</ins><span class="cx" style="display: block; padding: 0 10px">
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- if ( false === $this->options['public'] ) {
- $safelist = array_merge( $safelist, $this->private_data_fields );
- }
-
</del><span class="cx" style="display: block; padding: 0 10px"> array_walk( $data, function ( &$row ) use ( $safelist ) {
</span><span class="cx" style="display: block; padding: 0 10px"> $row = shortcode_atts( $safelist, $row );
</span><span class="cx" style="display: block; padding: 0 10px"> } );
</span></span></pre></div>
<a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampreportsclassesreportclasswordcampdetailsphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/classes/report/class-wordcamp-details.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/classes/report/class-wordcamp-details.php (rev 0)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/classes/report/class-wordcamp-details.php 2018-07-28 00:46:28 UTC (rev 7516)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,484 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/**
+ * @package WordCamp\Reports
+ */
+
+namespace WordCamp\Reports\Report;
+defined( 'WPINC' ) || die();
+
+use Exception;
+use DateTime;
+use WP_Post;
+use WordCamp\Reports;
+use WordCamp\Reports\Report\WordCamp_Status;
+use WordCamp\Reports\Utility\Date_Range;
+use function WordCamp\Reports\Validation\{validate_date_range, validate_wordcamp_status};
+use function WordCamp\Reports\Time\modify_cache_expiration_for_date_range;
+use WordCamp_Admin, WordCamp_Loader;
+use WordCamp\Utilities\Export_CSV;
+
+/**
+ * Class WordCamp_Details
+ *
+ * A report class for exporting a spreadsheet of WordCamps.
+ *
+ * @package WordCamp\Reports\Report
+ */
+class WordCamp_Details extends Base {
+ /**
+ * Report name.
+ *
+ * @var string
+ */
+ public static $name = 'WordCamp Details';
+
+ /**
+ * Report slug.
+ *
+ * @var string
+ */
+ public static $slug = 'wordcamp-details';
+
+ /**
+ * Report description.
+ *
+ * @var string
+ */
+ public static $description = 'Details about WordCamps occurring within a specified date range.';
+
+ /**
+ * Report methodology.
+ *
+ * @var string
+ */
+ public static $methodology = "
+ <ol>
+ <li>Retrieve WordCamp posts that fit within the date range and other optional criteria.</li>
+ <li>Extract the post meta values for each post that match the fields requested.</li>
+ <li>Walk all of the extracted data and format it for display.</li>
+ </ol>
+ ";
+
+ /**
+ * Report group.
+ *
+ * @var string
+ */
+ public static $group = 'wordcamp';
+
+ /**
+ * The date range that defines the scope of the report data.
+ *
+ * @var null|Date_Range
+ */
+ public $range = null;
+
+ /**
+ * The status to filter for in the report.
+ *
+ * @var string
+ */
+ public $status = '';
+
+ /**
+ * The fields to include in the report output.
+ *
+ * @var array
+ */
+ public $fields = [];
+
+ /**
+ * Data fields that can be visible in a public context.
+ *
+ * @var array An associative array of key/default value pairs.
+ */
+ protected $public_data_fields = [];
+
+ /**
+ * Data fields that should only be visible in a private context.
+ *
+ * @var array An associative array of key/default value pairs.
+ */
+ protected $private_data_fields = [];
+
+ /**
+ * WordCamp_Details constructor.
+ *
+ * @param string $start_date The start of the date range for the report.
+ * @param string $end_date The end of the date range for the report.
+ * @param string $status Optional. The status ID to filter for in the report.
+ * @param array $fields Not implemented yet.
+ * @param array $options {
+ * Optional. Additional report parameters.
+ * See Base::__construct and the functions in WordCamp\Reports\Validation for additional parameters.
+ *
+ * @type bool $include_dateless True to include WordCamps that don't have a date set. Default false.
+ * }
+ */
+ public function __construct( $start_date, $end_date, $status = '', array $fields = [], array $options = [] ) {
+ // Report-specific options.
+ $options = wp_parse_args( $options, array(
+ 'include_dateless' => false,
+ ) );
+
+ parent::__construct( $options );
+
+ try {
+ $this->range = validate_date_range( $start_date, $end_date, $options );
+ } catch ( Exception $e ) {
+ $this->error->add(
+ self::$slug . '-date-error',
+ $e->getMessage()
+ );
+ }
+
+ if ( $status && 'any' !== $status ) {
+ try {
+ $this->status = validate_wordcamp_status( $status, $options );
+ } catch ( Exception $e ) {
+ $this->error->add(
+ self::$slug . '-status-error',
+ $e->getMessage()
+ );
+ }
+ }
+
+ try {
+ $this->fields = $this->validate_fields_input( $fields );
+ } catch ( Exception $e ) {
+ $this->error->add(
+ self::$slug . '-fields-error',
+ $e->getMessage()
+ );
+ }
+
+ $this->public_data_fields = array_fill_keys( array_merge(
+ [
+ 'ID',
+ 'Name',
+ 'Status',
+ ],
+ WordCamp_Loader::get_public_meta_keys()
+ ), '' );
+
+ $this->private_data_fields = array_fill_keys( array_diff(
+ $this->get_meta_keys(),
+ array_keys( $this->public_data_fields )
+ ), '' );
+ }
+
+ /**
+ * TODO
+ *
+ * @param array $fields
+ */
+ protected function validate_fields_input( array $fields ) {}
+
+ /**
+ * Generate a cache key.
+ *
+ * @return string
+ */
+ protected function get_cache_key() {
+ $cache_key = parent::get_cache_key() . '_' . $this->range->start->getTimestamp() . '-' . $this->range->end->getTimestamp();
+
+ if ( $this->status ) {
+ $cache_key .= '_' . $this->status;
+ }
+
+ return $cache_key;
+ }
+
+ /**
+ * Generate a cache expiration interval.
+ *
+ * @return int A time interval in seconds.
+ */
+ protected function get_cache_expiration() {
+ $original_expiration = parent::get_cache_expiration();
+
+ try {
+ $expiration = modify_cache_expiration_for_date_range(
+ $original_expiration,
+ $this->range
+ );
+ } catch ( Exception $e ) {
+ $this->error->add(
+ self::$slug . '-cache-error',
+ $e->getMessage()
+ );
+
+ return $original_expiration;
+ }
+
+ return $expiration;
+ }
+
+ /**
+ * Query and parse the data for the report.
+ *
+ * @return array
+ */
+ public function get_data() {
+ // Bail if there are errors.
+ if ( ! empty( $this->error->get_error_messages() ) ) {
+ return array();
+ }
+
+ // Maybe use cached data.
+ $data = $this->maybe_get_cached_data();
+ if ( is_array( $data ) ) {
+ return $data;
+ }
+
+ $data = [];
+
+ $wordcamp_posts = $this->get_wordcamp_posts();
+
+ foreach ( $wordcamp_posts as $post ) {
+ $data[] = $this->extract_wordcamp_fields( $post );
+ }
+
+ $data = $this->filter_data_fields( $data );
+ $this->maybe_cache_data( $data );
+
+ return $data;
+ }
+
+ /**
+ * Compile the report data into results.
+ *
+ * Currently unused.
+ *
+ * @param array $data The data to compile.
+ *
+ * @return array
+ */
+ public function compile_report_data( array $data ) {
+ return $data;
+ }
+
+ /**
+ * Format the data for human-readable display.
+ *
+ * @param array $data The data to prepare.
+ *
+ * @return array
+ */
+ protected function prepare_data_for_display( array $data ) {
+ $all_statuses = WordCamp_Loader::get_post_statuses();
+
+ array_walk( $data, function( &$row ) use ( $all_statuses ) {
+ foreach ( $row as $key => $value ) {
+ switch ( $key ) {
+ case 'Status':
+ $row[ $key ] = $all_statuses[ $value ];
+ break;
+ case 'Start Date (YYYY-mm-dd)':
+ case 'End Date (YYYY-mm-dd)':
+ case 'Contributor Day Date (YYYY-mm-dd)':
+ $row[ $key ] = ( $value ) ? date( 'Y-m-d', $value ) : '';
+ break;
+ case 'Exhibition Space Available':
+ case 'Contributor Day':
+ $row[ $key ] = ( $value ) ? 'Yes' : 'No';
+ break;
+ case '_venue_coordinates':
+ if ( is_array( $value ) ) {
+ $row[ $key ] = implode( ', ', $value );
+ }
+ break;
+ }
+ }
+ } );
+
+ return $data;
+ }
+
+ /**
+ * Get all current WordCamp posts.
+ *
+ * @return array
+ */
+ protected function get_wordcamp_posts() {
+ $post_args = array(
+ 'post_type' => WCPT_POST_TYPE_ID,
+ 'post_status' => 'any',
+ 'posts_per_page' => 9999,
+ 'nopaging' => true,
+ 'no_found_rows' => false,
+ 'ignore_sticky_posts' => true,
+ 'orderby' => 'meta_value_num title',
+ 'order' => 'ASC',
+ 'meta_query' => [
+ [
+ 'key' => 'Start Date (YYYY-mm-dd)',
+ 'value' => array( $this->range->start->getTimestamp(), $this->range->end->getTimestamp() ),
+ 'compare' => 'BETWEEN',
+ 'type' => 'NUMERIC',
+ ],
+ ],
+ );
+
+ if ( $this->options['include_dateless'] ) {
+ $post_args['meta_query'] = array_merge( $post_args['meta_query'], [
+ 'relation' => 'OR',
+ [
+ 'key' => 'Start Date (YYYY-mm-dd)',
+ 'compare' => 'NOT EXISTS',
+ ],
+ [
+ 'key' => 'Start Date (YYYY-mm-dd)',
+ 'compare' => '=',
+ 'value' => '',
+ ],
+ ] );
+ }
+
+ if ( $this->options['public'] ) {
+ $post_args['post_status'] = WordCamp_Loader::get_public_post_statuses();
+ }
+
+ if ( $this->status ) {
+ $status_report = new WordCamp_Status(
+ $this->range->start->format( 'Y-m-d' ),
+ $this->range->end->format( 'Y-m-d' ),
+ $this->status,
+ $this->options
+ );
+
+ $post_ids = array_keys( $status_report->get_data() );
+
+ if ( empty( $post_ids ) ) {
+ return [];
+ }
+
+ $post_args['post__in'] = $post_ids;
+ }
+
+ return get_posts( $post_args );
+ }
+
+ /**
+ * Get the values of all the relevant post meta keys for a WordCamp post.
+ *
+ * @param WP_Post $wordcamp
+ *
+ * @return array
+ */
+ protected function extract_wordcamp_fields( WP_Post $wordcamp ) {
+ $meta_keys = $this->get_meta_keys();
+
+ $row = [
+ 'ID' => $wordcamp->ID,
+ 'Name' => $wordcamp->post_title,
+ 'Status' => $wordcamp->post_status,
+ ];
+
+ foreach ( $meta_keys as $key ) {
+ $row[ $key ] = get_post_meta( $wordcamp->ID, $key, true ) ?: '';
+ }
+
+ return $row;
+ }
+
+ /**
+ * Get a list of all the relevant meta keys for WordCamp posts.
+ *
+ * @return array
+ */
+ protected function get_meta_keys() {
+ /* @var WordCamp_Admin $wordcamp_admin */
+ global $wordcamp_admin;
+ $meta_keys = array_merge( array_keys( $wordcamp_admin->meta_keys( 'all' ) ), [
+ '_venue_coordinates',
+ '_venue_city',
+ '_venue_state',
+ '_venue_country_code',
+ '_venue_country_name',
+ '_venue_zip',
+ ] );
+
+ return $meta_keys;
+ }
+
+ /**
+ * Render the page for this report in the WP Admin.
+ *
+ * @return void
+ */
+ public static function render_admin_page() {
+ $start_date = filter_input( INPUT_POST, 'start-date' );
+ $end_date = filter_input( INPUT_POST, 'end-date' );
+ $include_dateless = filter_input( INPUT_POST, 'include_dateless', FILTER_VALIDATE_BOOLEAN );
+ $status = filter_input( INPUT_POST, 'status' );
+ $refresh = filter_input( INPUT_POST, 'refresh', FILTER_VALIDATE_BOOLEAN );
+ $action = filter_input( INPUT_POST, 'action' );
+ $nonce = filter_input( INPUT_POST, self::$slug . '-nonce' );
+ $statuses = WordCamp_Loader::get_post_statuses();
+
+ include Reports\get_views_dir_path() . 'report/wordcamp-details.php';
+ }
+
+ /**
+ * Export the report data to a file.
+ *
+ * @return void
+ */
+ public static function export_to_file() {
+ $start_date = filter_input( INPUT_POST, 'start-date' );
+ $end_date = filter_input( INPUT_POST, 'end-date' );
+ $include_dateless = filter_input( INPUT_POST, 'include_dateless', FILTER_VALIDATE_BOOLEAN );
+ $status = filter_input( INPUT_POST, 'status' );
+ $refresh = filter_input( INPUT_POST, 'refresh', FILTER_VALIDATE_BOOLEAN );
+ $action = filter_input( INPUT_POST, 'action' );
+ $nonce = filter_input( INPUT_POST, self::$slug . '-nonce' );
+
+ $report = null;
+
+ if ( 'Export CSV' !== $action ) {
+ return;
+ }
+
+ if ( wp_verify_nonce( $nonce, 'run-report' ) && current_user_can( 'manage_network' ) ) {
+ $options = array(
+ 'public' => false,
+ 'include_dateless' => $include_dateless,
+ 'earliest_start' => new DateTime( '2006-01-01' ), // No WordCamp posts before 2006.
+ );
+
+ if ( $status ) {
+ $options['earliest_start'] = new DateTime( '2015-01-01' ); // No status log data before 2015.
+ }
+
+ if ( $refresh ) {
+ $options['flush_cache'] = true;
+ }
+
+ $report = new self( $start_date, $end_date, $status, [], $options );
+
+ $filename = [ $report::$name ];
+ $filename[] = $report->range->start->format( 'Y-m-d' );
+ $filename[] = $report->range->end->format( 'Y-m-d' );
+ if ( $report->status ) {
+ $filename[] = $report->status;
+ }
+
+ $data = $report->prepare_data_for_display( $report->get_data() );
+
+ $headers = ( ! empty( $data ) ) ? array_keys( $data[0] ) : [];
+
+ $exporter = new Export_CSV( array(
+ 'filename' => $filename,
+ 'headers' => $headers,
+ 'data' => $data,
+ ) );
+
+ if ( ! empty( $report->error->get_error_messages() ) ) {
+ $exporter->error = $report->merge_errors( $report->error, $exporter->error );
+ }
+
+ $exporter->emit_file();
+ } // End if().
+ }
+}
</ins></span></pre></div>
<a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampreportsclassesreportclasswordcampstatusphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/classes/report/class-wordcamp-status.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/classes/report/class-wordcamp-status.php 2018-07-27 17:02:38 UTC (rev 7515)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/classes/report/class-wordcamp-status.php 2018-07-28 00:46:28 UTC (rev 7516)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -6,8 +6,10 @@
</span><span class="cx" style="display: block; padding: 0 10px"> namespace WordCamp\Reports\Report;
</span><span class="cx" style="display: block; padding: 0 10px"> defined( 'WPINC' ) || die();
</span><span class="cx" style="display: block; padding: 0 10px">
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+use Exception;
+use WordCamp\Reports;
+use function WordCamp\Reports\Validation\validate_wordcamp_status;
</ins><span class="cx" style="display: block; padding: 0 10px"> use WordCamp_Loader;
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-use WordCamp\Reports;
</del><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><span class="cx" style="display: block; padding: 0 10px"> * Class WordCamp_Status
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -100,45 +102,21 @@
</span><span class="cx" style="display: block; padding: 0 10px"> public function __construct( $start_date, $end_date, $status = '', array $options = array() ) {
</span><span class="cx" style="display: block; padding: 0 10px"> // Report-specific options.
</span><span class="cx" style="display: block; padding: 0 10px"> $options = wp_parse_args( $options, array(
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- 'status_subset' => array(),
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ 'status_subset' => [],
</ins><span class="cx" style="display: block; padding: 0 10px"> ) );
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> parent::__construct( $start_date, $end_date, $options );
</span><span class="cx" style="display: block; padding: 0 10px">
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- if ( 'any' === $status ) {
- $status = '';
- }
-
- if ( $status && $this->validate_status_input( $status ) ) {
- $this->status = $status;
- }
- }
-
- /**
- * Validate the given status ID string.
- *
- * @param string $status The status ID to filter for in the report.
- *
- * @return bool True if the status ID is valid. Otherwise false.
- */
- protected function validate_status_input( $status ) {
- if ( is_array( $this->options['status_subset'] ) && ! empty( $this->options['status_subset'] ) ) {
- if ( ! in_array( $status, $this->options['status_subset'], true ) ) {
- $this->error->add( 'invalid_status', 'Please enter a valid status ID.' );
-
- return false;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ if ( $status && 'any' !== $status ) {
+ try {
+ $this->status = validate_wordcamp_status( $status, $options );
+ } catch ( Exception $e ) {
+ $this->error->add(
+ self::$slug . '-status-error',
+ $e->getMessage()
+ );
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-
- return true;
</del><span class="cx" style="display: block; padding: 0 10px"> }
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-
- if ( ! in_array( $status, array_keys( WordCamp_Loader::get_post_statuses() ), true ) ) {
- $this->error->add( 'invalid_status', 'Please enter a valid status ID.' );
-
- return false;
- }
-
- return true;
</del><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span></span></pre></div>
<a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampreportsclassesutilityclassdaterangephp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/classes/utility/class-date-range.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/classes/utility/class-date-range.php (rev 0)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/classes/utility/class-date-range.php 2018-07-28 00:46:28 UTC (rev 7516)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,56 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+
+namespace WordCamp\Reports\Utility;
+defined( 'WPINC' ) || die();
+
+use DateTimeInterface, DateInterval;
+
+/**
+ * Class Date_Range
+ * @package WordCamp\Reports\Utility
+ */
+class Date_Range {
+ /**
+ * The start date of the range.
+ *
+ * @var DateTimeInterface|null
+ */
+ public $start = null;
+
+ /**
+ * The end date of the range.
+ *
+ * @var DateTimeInterface|null
+ */
+ public $end = null;
+
+ /**
+ * The interval between the start and end dates.
+ *
+ * @var DateInterval|null
+ */
+ public $interval = null;
+
+ /**
+ * Date_Range constructor.
+ *
+ * @param DateTimeInterface $start
+ * @param DateTimeInterface $end
+ */
+ public function __construct( DateTimeInterface $start, DateTimeInterface $end ) {
+ $this->start = $start;
+ $this->end = $end;
+ $this->interval = $end->diff( $start );
+ }
+
+ /**
+ * Test if a date is within the range.
+ *
+ * @param DateTimeInterface $date The date to test.
+ *
+ * @return bool
+ */
+ public function is_within( DateTimeInterface $date ) {
+ return $date >= $this->start && $date <= $this->end;
+ }
+}
</ins></span></pre></div>
<a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampreportsincludestimephp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/includes/time.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/includes/time.php (rev 0)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/includes/time.php 2018-07-28 00:46:28 UTC (rev 7516)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,144 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+
+namespace WordCamp\Reports\Time;
+defined( 'WPINC' ) || die();
+
+use Exception;
+use DateTimeImmutable;
+use WordCamp\Reports\Utility\Date_Range;
+
+/**
+ * Generate a simple array of years.
+ *
+ * @param int $start_year The first year in the array.
+ * @param int $end_year The last year in the array.
+ *
+ * @return array
+ */
+function year_array( int $start_year, int $end_year ) {
+ return range( $start_year, $end_year, 1 );
+}
+
+/**
+ * Generate an associative array of quarters, with abbreviation keys and label values.
+ *
+ * @return array
+ */
+function quarter_array() {
+ return array(
+ 'q1' => '1st quarter',
+ 'q2' => '2nd quarter',
+ 'q3' => '3rd quarter',
+ 'q4' => '4th quarter',
+ );
+}
+
+/**
+ * Generate an associative array of months, with numerical keys and string values.
+ *
+ * @return array
+ */
+function month_array() {
+ $months = array();
+
+ foreach ( range( 1, 12 ) as $number ) {
+ $months[ $number ] = date( 'F', mktime( 0, 0, 0, $number, 10 ) );
+ }
+
+ return $months;
+}
+
+/**
+ * Convert a representation of a time period within a given year into a date range.
+ *
+ * @param int $year The year containing the time period.
+ * @param string|int $period The time period to convert. E.g. 2, 'February', 'q1'.
+ *
+ * @return Date_Range An object representing the valid date range.
+ * @throws Exception
+ */
+function convert_time_period_to_date_range( $year, $period = '' ) {
+ if ( ! is_int( $year ) ) {
+ throw new Exception( 'Invalid year.' );
+ }
+
+ $months = month_array();
+
+ $start_date = '';
+ $end_date = '';
+
+ if ( ! $period || 'all' === $period ) {
+ // Period is the entire year.
+ $start_date = "$year-01-01";
+ $end_date = "$year-12-31";
+ } elseif ( array_key_exists( $period, quarter_array() ) ) {
+ // Period is a quarter.
+ switch ( $period ) {
+ case 'q1' :
+ $start_date = "$year-01-01";
+ break;
+ case 'q2' :
+ $start_date = "$year-04-01";
+ break;
+ case 'q3' :
+ $start_date = "$year-07-01";
+ break;
+ case 'q4' :
+ $start_date = "$year-10-01";
+ break;
+ }
+
+ $end_date = date( 'Y-m-d', strtotime( '+ 3 months - 1 second', strtotime( $start_date ) ) );
+ } elseif ( array_key_exists( $period, $months ) || in_array( $period, $months, true ) ) {
+ // Period is a specific month.
+ if ( in_array( $period, $months, true ) ) {
+ // Month name given. Convert it to a number.
+ $period = array_search( $period, $months, true );
+ }
+
+ $start_date = "$year-$period-01";
+ $end_date = date( 'Y-m-d', strtotime( '+ 1 month - 1 second', strtotime( $start_date ) ) );
+ }
+
+ if ( ! $start_date || ! $end_date ) {
+ throw new Exception( 'Invalid time period.' );
+ }
+
+ try {
+ $range = new Date_Range(
+ new DateTimeImmutable( $start_date ),
+ new DateTimeImmutable( $end_date )
+ );
+ } catch ( Exception $e ) {
+ throw new Exception( sprintf(
+ 'Invalid range: %s',
+ $e->getMessage()
+ ) );
+ }
+
+ return $range;
+}
+
+/**
+ * Change the expiration time interval based on the current date/time relative to a date range.
+ *
+ * @param int $expiration A time interval in seconds.
+ * @param Date_Range $range
+ *
+ * @return int A (possibly) modified time interval in seconds.
+ * @throws Exception
+ */
+function modify_cache_expiration_for_date_range( $expiration, Date_Range $range ) {
+ $now = new DateTimeImmutable( 'now' );
+ $now->setTime( 0, 0, 0 ); // Beginning of the current day.
+
+ if ( $range->is_within( $now ) ) {
+ // Expire the cache sooner if the data includes the current day.
+ $expiration = HOUR_IN_SECONDS;
+ } elseif ( $range->end->diff( $now )->y > 0 ) {
+ // Keep the cache longer if the end of the date range is over a year ago.
+ $expiration = MONTH_IN_SECONDS;
+ }
+
+ return $expiration;
+}
</ins></span></pre></div>
<a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampreportsincludesvalidationphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/includes/validation.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/includes/validation.php (rev 0)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/includes/validation.php 2018-07-28 00:46:28 UTC (rev 7516)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,187 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+
+namespace WordCamp\Reports\Validation;
+defined( 'WPINC' ) || die();
+
+use Exception;
+use DateTime, DateTimeImmutable, DateInterval;
+use WP_Post;
+use WordCamp\Reports\Utility\Date_Range;
+use WordCamp_Loader;
+
+/**
+ * Validate strings for start and end dates in a date range.
+ *
+ * @param string $start_date A string representation of the beginning of the date range.
+ * @param string $end_date A string representation of the end of the date range.
+ * @param array $config {
+ * Optional. Modify the default configuration of the validator.
+ *
+ * @type bool $allow_future_start True to allow the date range to start in the future. Default false.
+ * @type bool $allow_future_end True to allow the date range to extend into the future. Default true.
+ * @type DateTime|null $earliest_start The earliest date that can be used for the start of the date range.
+ * @type DateInterval|null $max_interval The maximum interval of time between the start and end dates.
+ * @type bool $include_end_date True to include the full ending day in the date range.
+ * }
+ *
+ * @return Date_Range An object representing the valid date range.
+ * @throws Exception
+ */
+function validate_date_range( $start_date, $end_date, array $config = [] ) {
+ $config_defaults = [
+ 'allow_future_start' => false,
+ 'allow_future_end' => true,
+ 'earliest_start' => null,
+ 'max_interval' => new DateInterval( 'P1Y' ),
+ 'include_end_date' => true,
+ ];
+
+ $config = wp_parse_args( $config, $config_defaults );
+
+ if ( ! $start_date || ! $end_date ) {
+ throw new Exception( 'Please enter valid start and end dates.' );
+ }
+
+ try {
+ $start_date = new DateTimeImmutable( $start_date ); // Immutable so methods don't modify the original object.
+ } catch ( Exception $e ) {
+ throw new Exception( sprintf(
+ 'Invalid start date: %s',
+ $e->getMessage()
+ ) );
+ }
+
+ // No future start dates.
+ if ( ! $config['allow_future_start'] && $start_date > date_create( 'now' ) ) {
+ throw new Exception( 'Please enter a start date that is the same as or before today\'s date.' );
+ }
+
+ // Check for start date boundary.
+ if ( $config['earliest_start'] instanceof DateTime && $start_date < $config['earliest_start'] ) {
+ throw new Exception( sprintf(
+ 'Please enter a start date of %s or later.',
+ $config['earliest_start']->format( 'Y-m-d' )
+ ) );
+ }
+
+ try {
+ $end_date = new DateTimeImmutable( $end_date ); // Immutable so methods don't modify the original object.
+ } catch ( Exception $e ) {
+ throw new Exception( sprintf(
+ 'Invalid end date: %s',
+ $e->getMessage()
+ ) );
+ }
+
+ // No negative date intervals.
+ if ( $start_date > $end_date ) {
+ throw new Exception( 'Please enter an end date that is the same as or after the start date.' );
+ }
+
+ // Check for date interval boundary.
+ if ( $config['max_interval'] instanceof DateInterval ) {
+ $max_end_date = $start_date->add( $config['max_interval'] );
+
+ if ( $end_date > $max_end_date ) {
+ throw new Exception( sprintf(
+ 'Please enter an end date that is no more than %s days after the start date.',
+ $start_date->diff( $max_end_date )->format( '%a' )
+ ) );
+ }
+ }
+
+ // If the end date doesn't have a specific time, make sure the entire day is included.
+ if ( $config['include_end_date'] && '00:00:00' === $end_date->format( 'H:i:s' ) ) {
+ $end_date->setTime( 23, 59, 59 );
+ }
+
+ return new Date_Range( $start_date, $end_date );
+}
+
+/**
+ * Validate a WordCamp post ID.
+ *
+ * @param int $post_id The ID of a WCPT post.
+ * @param array $config {
+ * Optional. Modify the default configuration of the validator.
+ *
+ * @type bool $require_site True if the WordCamp post must have an associated site in the network.
+ * }
+ *
+ * @return array An associative array containing valid post ID and site ID integers for the WordCamp.
+ * @throws Exception
+ */
+function validate_wordcamp_id( $post_id, array $config = [] ) {
+ $config_defaults = [
+ 'require_site' => true,
+ ];
+
+ $config = wp_parse_args( $config, $config_defaults );
+
+ $switched = false;
+
+ if ( BLOG_ID_CURRENT_SITE !== get_current_blog_id() ) {
+ $switched = switch_to_blog( BLOG_ID_CURRENT_SITE );
+ }
+
+ $wordcamp_post = get_post( $post_id );
+
+ if ( ! $wordcamp_post instanceof WP_Post || WCPT_POST_TYPE_ID !== get_post_type( $wordcamp_post ) ) {
+ throw new Exception( 'Please enter a valid WordCamp ID.' );
+ }
+
+ $valid = [
+ 'post_id' => $post_id,
+ 'site_id' => 0,
+ ];
+
+ if ( $config['require_site'] ) {
+ $site_id = get_wordcamp_site_id( $wordcamp_post );
+
+ if ( ! $site_id ) {
+ throw new Exception( 'The specified WordCamp does not have a site yet.' );
+ }
+
+ $valid['site_id'] = $site_id;
+ }
+
+ if ( $switched ) {
+ restore_current_blog();
+ }
+
+ return $valid;
+}
+
+/**
+ * Validate a WordCamp status ID string.
+ *
+ * @param string $wordcamp_status A WordCamp status ID string.
+ * @param array $config {
+ * Optional. Modify the default configuration of the validator.
+ *
+ * @type array $status_subset An array of status ID strings that should be considered valid.
+ * }
+ *
+ * @return string The validated WordCamp status ID string.
+ * @throws Exception
+ */
+function validate_wordcamp_status( $wordcamp_status, array $config = [] ) {
+ $config_defaults = [
+ 'status_subset' => [],
+ ];
+
+ $config = wp_parse_args( $config, $config_defaults );
+
+ $valid_statuses = array_keys( WordCamp_Loader::get_post_statuses() );
+ $subset = array_intersect( $config['status_subset'], $valid_statuses );
+
+ if ( ! empty( $subset ) ) {
+ $valid_statuses = $subset;
+ }
+
+ if ( ! in_array( $wordcamp_status, $valid_statuses, true ) ) {
+ throw new Exception( 'Please enter a valid status ID.' );
+ }
+
+ return $wordcamp_status;
+}
</ins></span></pre></div>
<a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampreportsindexphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/index.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/index.php 2018-07-27 17:02:38 UTC (rev 7515)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/index.php 2018-07-28 00:46:28 UTC (rev 7516)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -31,6 +31,15 @@
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * Get the path for the includes directory.
+ *
+ * @return string Path with trailing slash.
+ */
+function get_includes_dir_path() {
+ return trailingslashit( PLUGIN_DIR ) . 'includes/';
+}
+
+/**
</ins><span class="cx" style="display: block; padding: 0 10px"> * Get the path for the views directory.
</span><span class="cx" style="display: block; padding: 0 10px"> *
</span><span class="cx" style="display: block; padding: 0 10px"> * @return string Path with trailing slash.
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -58,6 +67,21 @@
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * Autoload all the files in the includes directory.
+ *
+ * @return void
+ */
+function load_includes() {
+ foreach ( glob( get_includes_dir_path() . '*.php' ) as $filename ) {
+ if ( is_readable( $filename ) ) {
+ include_once ( $filename );
+ }
+ }
+}
+
+add_action( 'plugins_loaded', __NAMESPACE__ . '\load_includes' );
+
+/**
</ins><span class="cx" style="display: block; padding: 0 10px"> * Autoloader for plugin classes.
</span><span class="cx" style="display: block; padding: 0 10px"> *
</span><span class="cx" style="display: block; padding: 0 10px"> * @param string $class The fully-qualified class name.
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -109,6 +133,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> __NAMESPACE__ . '\Report\Sponsor_Invoices',
</span><span class="cx" style="display: block; padding: 0 10px"> __NAMESPACE__ . '\Report\Payment_Activity',
</span><span class="cx" style="display: block; padding: 0 10px"> __NAMESPACE__ . '\Report\Sponsorship_Grants',
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ __NAMESPACE__ . '\Report\WordCamp_Details',
</ins><span class="cx" style="display: block; padding: 0 10px"> __NAMESPACE__ . '\Report\WordCamp_Status',
</span><span class="cx" style="display: block; padding: 0 10px"> __NAMESPACE__ . '\Report\Meetup_Groups',
</span><span class="cx" style="display: block; padding: 0 10px"> __NAMESPACE__ . '\Report\WordCamp_Payment_Methods',
</span></span></pre></div>
<a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampreportsviewsreportwordcampdetailsphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/views/report/wordcamp-details.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/views/report/wordcamp-details.php (rev 0)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/views/report/wordcamp-details.php 2018-07-28 00:46:28 UTC (rev 7516)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,70 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/**
+ * @package WordCamp\Reports
+ */
+
+namespace WordCamp\Reports\Views\Report\WordCamp_Details;
+defined( 'WPINC' ) || die();
+
+use WordCamp\Reports;
+use WordCamp\Reports\Report;
+
+/** @var string $start_date */
+/** @var string $end_date */
+/** @var bool $include_dateless */
+/** @var string $status */
+/** @var array $statuses */
+?>
+
+<div class="wrap">
+ <h1>
+ <a href="<?php echo esc_attr( Reports\get_page_url() ); ?>">WordCamp Reports</a>
+ »
+ <?php echo esc_html( Report\WordCamp_Details::$name ); ?>
+ </h1>
+
+ <?php echo wpautop( wp_kses_post( Report\WordCamp_Details::$description ) ); ?>
+
+ <h4>Methodology</h4>
+
+ <?php echo wpautop( wp_kses_post( Report\WordCamp_Details::$methodology ) ); ?>
+
+ <form method="post" action="">
+ <input type="hidden" name="action" value="run-report" />
+ <?php wp_nonce_field( 'run-report', Report\WordCamp_Details::$slug . '-nonce' ); ?>
+
+ <table class="form-table">
+ <tbody>
+ <tr>
+ <th scope="row"><label for="start-date">Start Date</label></th>
+ <td><input type="date" id="start-date" name="start-date" value="<?php echo esc_attr( $start_date ) ?>" required /></td>
+ </tr>
+ <tr>
+ <th scope="row"><label for="end-date">End Date</label></th>
+ <td><input type="date" id="end-date" name="end-date" value="<?php echo esc_attr( $end_date ) ?>" required /></td>
+ </tr>
+ <tr>
+ <th scope="row"><label for="include_dateless">Include WordCamps without a date</label></th>
+ <td><input type="checkbox" id="include_dateless" name="include_dateless"<?php checked( $include_dateless ); ?> /></td>
+ </tr>
+ <tr>
+ <th scope="row"><label for="status">Status (optional)</label></th>
+ <td>
+ <select id="status" name="status">
+ <option value="any"<?php selected( ( ! $status || 'any' === $status ) ); ?>>Any</option>
+ <?php foreach ( $statuses as $value => $label ) : ?>
+ <option value="<?php echo esc_attr( $value ); ?>"<?php selected( $value, $status ); ?>><?php echo esc_attr( $label ); ?></option>
+ <?php endforeach; ?>
+ </select>
+ </td>
+ </tr>
+ <tr>
+ <th scope="row"><label for="refresh">Refresh results</label></th>
+ <td><input type="checkbox" id="refresh" name="refresh" /></td>
+ </tr>
+ </tbody>
+ </table>
+
+ <?php submit_button( 'Export CSV', 'primary', 'action', false ); ?>
+ </form>
+</div>
</ins></span></pre>
</div>
</div>
</body>
</html>