Making WordPress.org

Changeset 7588


Ignore:
Timestamp:
08/02/2018 07:12:19 PM (6 years ago)
Author:
coreymckrill
Message:

WordCamp Reports: Updates to WordCamp Status and WordCamp Details reports

  • Simplify the Details report, making all criteria optional, and adding a parameter so specific WordCamp IDs can be submitted.
  • Rework the UI of the Status report so that it utilizes the Details report for exporting data about WordCamps that appear in the results.
Location:
sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports
Files:
9 edited

Legend:

Unmodified
Added
Removed
  • sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/assets/js/wordcamp-status.js

    r6662 r7588  
    1818            };
    1919
     20            this.setupFieldsToggle();
    2021            this.setupSingleToggles();
    2122            this.setupBulkToggles();
    2223
     24            $( '#status' ).select2();
     25
    2326            $( document ).ready( function() {
    2427                self.cache.$hideAll.trigger( 'click' );
     28            } );
     29        },
     30
     31        setupFieldsToggle: function() {
     32            var self = this;
     33
     34            self.cache.$fieldsToggle = $( '#fields-toggle' );
     35            self.cache.$fieldsContainer = $( '#fields-section' );
     36
     37            self.cache.$fieldsToggle.on( 'click', function( event ) {
     38                event.preventDefault();
     39
     40                if ( self.cache.$fieldsContainer.hasClass( 'hidden' ) ) {
     41                    self.cache.$fieldsContainer.removeClass( 'hidden' );
     42                } else {
     43                    self.cache.$fieldsContainer.addClass( 'hidden' );
     44                }
    2545            } );
    2646        },
  • sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/classes/report/class-wordcamp-details.php

    r7550 r7588  
    99use Exception;
    1010use DateTime;
    11 use WP_Post, WP_Query;
     11use WP_Post, WP_Query, WP_Error;
    1212use function WordCamp\Reports\{get_assets_url, get_assets_dir_path, get_views_dir_path};
    1313use WordCamp\Reports\Utility\Date_Range;
    14 use function WordCamp\Reports\Validation\{validate_date_range, validate_wordcamp_id, validate_wordcamp_status};
     14use function WordCamp\Reports\Validation\{validate_date_range, validate_wordcamp_id};
    1515use WordCamp_Admin, WordCamp_Loader;
    1616use WordCamp\Utilities\Export_CSV;
     
    2121 * A report class for exporting a spreadsheet of WordCamps.
    2222 *
     23 * Note that this report does not use caching because it is only used in WP Admin and has a large number of
     24 * optional parameters.
     25 *
    2326 * @package WordCamp\Reports\Report
    2427 */
     
    4346     * @var string
    4447     */
    45     public static $description = 'Details about WordCamps occurring within a specified date range.';
     48    public static $description = 'Create a spreadsheet of details about WordCamps that match optional criteria.';
    4649
    4750    /**
     
    5255    public static $methodology = "
    5356        <ol>
    54             <li>Retrieve WordCamp posts that fit within the date range and other optional criteria.</li>
     57            <li>Retrieve WordCamp posts that fit within the criteria.</li>
    5558            <li>Extract the data for each post that match the fields requested.</li>
    56             <li>Walk all of the extracted data and format it for display.</li>
     59            <li>Walk through all of the extracted data and format it for display.</li>
    5760        </ol>
    5861    ";
     
    6669
    6770    /**
    68      * The date range that defines the scope of the report data.
     71     * A date range that WordCamp events must fall within.
    6972     *
    7073     * @var null|Date_Range
     
    7376
    7477    /**
    75      * The status to filter for in the report.
    76      *
    77      * @var string
    78      */
    79     public $status = '';
    80 
    81     /**
    82      * Whether to include data for WordCamps that don't have a date set.
     78     * A list of WordCamp post IDs.
     79     *
     80     * @var array
     81     */
     82    public $wordcamp_ids = [];
     83
     84    /**
     85     * Whether to include counts of various post types for each WordCamp.
    8386     *
    8487     * @var bool
    8588     */
    86     public $include_dateless = false;
    87 
    88     /**
    89      * Whether to include counts of various post types for each WordCamp.
    90      *
    91      * @var bool
    92      */
    9389    public $include_counts = false;
    9490
     
    110106     * WordCamp_Details constructor.
    111107     *
    112      * @param string $start_date       The start of the date range for the report.
    113      * @param string $end_date         The end of the date range for the report.
    114      * @param string $status           Optional. The status ID to filter for in the report.
    115      * @param bool   $include_dateless Optional. True to include data for WordCamps that don't have a date set. Default false.
    116      * @param bool   $include_counts   Optional. True to include counts of various post types for each WordCamp. Default false.
    117      * @param array  $options          {
     108     * @param Date_Range $date_range       Optional. A date range that WordCamp events must fall within.
     109     * @param array      $wordcamp_ids     Optional. A list of WordCamp post IDs to include in the results.
     110     * @param bool       $include_counts   Optional. True to include counts of various post types for each WordCamp.
     111     *                                     Default false.
     112     * @param array      $options          {
    118113     *     Optional. Additional report parameters.
    119114     *     See Base::__construct and the functions in WordCamp\Reports\Validation for additional parameters.
     
    123118     * }
    124119     */
    125     public function __construct( $start_date, $end_date, $status = '', $include_dateless = false, $include_counts = false, array $options = [] ) {
     120    public function __construct( Date_Range $date_range = null, array $wordcamp_ids = [], $include_counts = false, array $options = [] ) {
    126121        // Report-specific options.
    127122        $options = wp_parse_args( $options, [
    128             'status_subset' => [],
    129             'fields'        => [],
     123            'fields' => [],
    130124        ] );
    131125
    132126        parent::__construct( $options );
    133127
    134         try {
    135             $this->range = validate_date_range( $start_date, $end_date, $options );
    136         } catch ( Exception $e ) {
    137             $this->error->add(
    138                 self::$slug . '-date-error',
    139                 $e->getMessage()
    140             );
    141         }
    142 
    143         if ( $status && 'any' !== $status ) {
    144             try {
    145                 $this->status = validate_wordcamp_status( $status, $options );
    146             } catch ( Exception $e ) {
    147                 $this->error->add(
    148                     self::$slug . '-status-error',
    149                     $e->getMessage()
    150                 );
    151             }
    152         }
    153 
    154         $this->include_dateless = wp_validate_boolean( $include_dateless );
    155         $this->include_counts   = wp_validate_boolean( $include_counts );
     128        if ( $date_range instanceof Date_Range ) {
     129            $this->range = $date_range;
     130        }
     131
     132        if ( ! empty( $wordcamp_ids ) ) {
     133            foreach ( $wordcamp_ids as $wordcamp_id ) {
     134                try {
     135                    $this->wordcamp_ids[] = validate_wordcamp_id( $wordcamp_id, [ 'require_site' => false ] )->post_id;
     136                } catch ( Exception $e ) {
     137                    $this->error->add(
     138                        self::$slug . '-wordcamp-id-error',
     139                        $e->getMessage()
     140                    );
     141
     142                    break;
     143                }
     144            }
     145        }
     146
     147        $this->include_counts = wp_validate_boolean( $include_counts );
    156148
    157149        $public_data_field_keys = array_merge(
    158150            [
    159151                'Name',
    160                 'Status',
    161152            ],
    162153            WordCamp_Loader::get_public_meta_keys()
     
    167158            [
    168159                'ID',
     160                'Created',
     161                'Status',
    169162                'Tickets',
    170163                'Speakers',
     
    211204
    212205    /**
    213      * Generate a cache key.
    214      *
    215      * @return string
    216      */
    217     protected function get_cache_key() {
    218         $cache_key_segments = [
    219             parent::get_cache_key(),
    220             $this->range->generate_cache_key_segment(),
    221         ];
    222 
    223         if ( $this->status ) {
    224             $cache_key_segments[] = $this->status;
    225         }
    226 
    227         if ( $this->include_dateless ) {
    228             $cache_key_segments[] = '+dateless';
    229         }
    230 
    231         if ( $this->include_counts ) {
    232             $cache_key_segments[] = '+counts';
    233         }
    234 
    235         return implode( '_', $cache_key_segments );
    236     }
    237 
    238     /**
    239      * Generate a cache expiration interval.
    240      *
    241      * @return int A time interval in seconds.
    242      */
    243     protected function get_cache_expiration() {
    244         return $this->range->generate_cache_duration( parent::get_cache_expiration() );
    245     }
    246 
    247     /**
    248206     * Query and parse the data for the report.
    249207     *
     
    254212        if ( ! empty( $this->error->get_error_messages() ) ) {
    255213            return array();
    256         }
    257 
    258         // Maybe use cached data.
    259         $data = $this->maybe_get_cached_data();
    260         if ( is_array( $data ) ) {
    261             return $data;
    262214        }
    263215
     
    277229            $row = array_intersect_key( array_replace( $field_order, $row ), $row );
    278230        } );
    279 
    280         $this->maybe_cache_data( $data );
    281231
    282232        return $data;
     
    312262            array_keys( $wordcamp_admin->meta_keys( 'wordcamp' ) ),
    313263            [
     264                'Created',
    314265                'Status',
    315266                'Tickets',
     
    339290     * @return array
    340291     */
    341     protected function prepare_data_for_display( array $data ) {
     292    public function prepare_data_for_display( array $data ) {
    342293        $all_statuses = WordCamp_Loader::get_post_statuses();
    343294
     
    381332
    382333    /**
    383      * Get all current WordCamp posts.
    384      *
    385      * @return array
     334     * Get WordCamp posts that fit the report criteria.
     335     *
     336     * @return array An array of WP_Post objects.
    386337     */
    387338    protected function get_wordcamp_posts() {
     
    393344            'no_found_rows'       => false,
    394345            'ignore_sticky_posts' => true,
    395             'orderby'             => 'meta_value_num title',
     346            'orderby'             => 'id',
    396347            'order'               => 'ASC',
    397             'meta_query'          => [
     348        );
     349
     350        if ( $this->range instanceof Date_Range ) {
     351            // This replaces the default meta query.
     352            $post_args['meta_query'] = [
    398353                [
    399354                    'key'      => 'Start Date (YYYY-mm-dd)',
     
    402357                    'type'     => 'NUMERIC',
    403358                ],
    404             ],
    405         );
    406 
    407         if ( $this->include_dateless ) {
    408             $post_args['meta_query'] = array_merge( $post_args['meta_query'], [
    409                 'relation' => 'OR',
    410                 [
    411                     'key'     => 'Start Date (YYYY-mm-dd)',
    412                     'compare' => 'NOT EXISTS',
    413                 ],
    414                 [
    415                     'key'     => 'Start Date (YYYY-mm-dd)',
    416                     'compare' => '=',
    417                     'value'   => '',
    418                 ],
    419             ] );
    420 
    421             // Don't include really old camps with no date or ones that didn't exist during the date range.
    422             $post_args['date_query'] = [
    423                 [
    424                     'before' => $this->range->end->format( 'Y-m-d' ),
    425                     'after'  => $this->range->start->format( 'Y-m-d' ) . ' - 1 year',
    426                 ],
    427359            ];
     360            $post_args['orderby'] = 'meta_value_num title';
     361        }
     362
     363        if ( ! empty( $this->wordcamp_ids ) ) {
     364            $post_args['post__in'] = $this->wordcamp_ids;
    428365        }
    429366
     
    432369        }
    433370
    434         if ( $this->status ) {
    435             $status_report = new WordCamp_Status(
    436                 $this->range->start->format( 'Y-m-d' ),
    437                 $this->range->end->format( 'Y-m-d' ),
    438                 $this->status,
    439                 $this->options
    440             );
    441 
    442             $post_ids = array_keys( $status_report->get_data() );
    443 
    444             if ( empty( $post_ids ) ) {
    445                 return [];
    446             }
    447 
    448             $post_args['post__in'] = $post_ids;
    449         }
    450 
    451371        return get_posts( $post_args );
    452372    }
     
    463383
    464384        $row = [
    465             'ID'     => $wordcamp->ID,
    466             'Name'   => $wordcamp->post_title,
    467             'Status' => $wordcamp->post_status,
     385            'ID'      => $wordcamp->ID,
     386            'Name'    => $wordcamp->post_title,
     387            'Created' => get_the_date( 'Y-m-d', $wordcamp->ID ),
     388            'Status'  => $wordcamp->post_status,
    468389        ];
    469390
     
    502423     * Count the number of various post types for a WordCamp.
    503424     *
     425     * If the WordCamp doesn't have a site yet, the counts will all be zero.
     426     *
    504427     * @param WP_Post $wordcamp
    505428     *
     
    515438
    516439        try {
    517             $ids = validate_wordcamp_id( $wordcamp->ID, [ 'require_site' => true ] );
     440            $id = validate_wordcamp_id( $wordcamp->ID, [ 'require_site' => false ] );
    518441        } catch ( Exception $e ) {
     442            return $counts;
     443        }
     444
     445        if ( ! $id->site_id ) {
    519446            return $counts;
    520447        }
     
    530457        };
    531458
    532         switch_to_blog( $ids['site_id'] );
     459        switch_to_blog( $id->site_id );
    533460
    534461        // Tickets
     
    549476
    550477    /**
     478     * Render HTML form inputs for the fields that are available for inclusion in the spreadsheet.
     479     *
     480     * @param string $context        'public' or 'private'. Default 'public'.
     481     * @param array  $field_defaults Optional. An associative array where the keys are field keys and the values
     482     *                               are extra attributes for those field inputs. Examples: checked or required.
     483     */
     484    public static function render_available_fields( $context = 'public', array $field_defaults = [] ) {
     485        $field_order      = array_fill_keys( self::get_field_order(), '' );
     486        $field_defaults   = array_replace( $field_order, $field_defaults );
     487
     488        $shadow_report = new self( null, [], false, [ 'public' => ( 'private' === $context ) ? false : true ] );
     489
     490        $available_fields = array_intersect_key( $field_defaults, $shadow_report->get_data_fields_safelist() );
     491        ?>
     492        <fieldset class="fields-container">
     493            <legend class="fields-label">Available Fields</legend>
     494
     495            <?php foreach ( $available_fields as $field_name => $extra_props ) : ?>
     496                <div class="field-checkbox">
     497                    <input
     498                        type="checkbox"
     499                        id="fields-<?php echo esc_attr( $field_name ); ?>"
     500                        name="fields[]"
     501                        value="<?php echo esc_attr( $field_name ); ?>"
     502                        <?php if ( $extra_props && is_string( $extra_props ) ) echo esc_html( $extra_props ); ?>
     503                    />
     504                    <label for="fields-<?php echo esc_attr( $field_name ); ?>">
     505                        <?php echo esc_attr( $field_name ); ?>
     506                    </label>
     507                </div>
     508            <?php endforeach; ?>
     509        </fieldset>
     510        <?php
     511    }
     512
     513    /**
    551514     * Register all assets used by this report.
    552515     *
    553516     * @return void
    554517     */
    555     protected static function register_assets() {
     518    public static function register_assets() {
    556519        wp_register_script(
    557520            self::$slug,
     
    589552     */
    590553    public static function render_admin_page() {
    591         $start_date       = filter_input( INPUT_POST, 'start-date' );
    592         $end_date         = filter_input( INPUT_POST, 'end-date' );
    593         $include_dateless = filter_input( INPUT_POST, 'include_dateless', FILTER_VALIDATE_BOOLEAN );
    594         $status           = filter_input( INPUT_POST, 'status' );
    595         $refresh          = filter_input( INPUT_POST, 'refresh', FILTER_VALIDATE_BOOLEAN );
    596         $action           = filter_input( INPUT_POST, 'action' );
    597         $nonce            = filter_input( INPUT_POST, self::$slug . '-nonce' );
    598         $statuses         = WordCamp_Loader::get_post_statuses();
    599 
    600         $field_order      = array_fill_keys( self::get_field_order(), '' );
    601         $field_defaults   = array_replace( $field_order, [
     554        $field_defaults = [
    602555            'ID'                      => 'checked',
    603556            'Name'                    => 'checked disabled',
     
    606559            'Location'                => 'checked',
    607560            'URL'                     => 'checked',
    608         ] );
    609 
    610         $shadow_report    = new self( '', '', '', false, false, [ 'public' => false ] );
    611         $available_fields = array_intersect_key( $field_defaults, $shadow_report->get_data_fields_safelist() );
     561        ];
    612562
    613563        include get_views_dir_path() . 'report/wordcamp-details.php';
     
    622572        $start_date       = filter_input( INPUT_POST, 'start-date' );
    623573        $end_date         = filter_input( INPUT_POST, 'end-date' );
    624         $include_dateless = filter_input( INPUT_POST, 'include_dateless', FILTER_VALIDATE_BOOLEAN );
    625         $status           = filter_input( INPUT_POST, 'status' );
    626574        $fields           = filter_input( INPUT_POST, 'fields', FILTER_SANITIZE_STRING, [ 'flags' => FILTER_REQUIRE_ARRAY ] );
    627         $refresh          = filter_input( INPUT_POST, 'refresh', FILTER_VALIDATE_BOOLEAN );
    628575        $action           = filter_input( INPUT_POST, 'action' );
    629576        $nonce            = filter_input( INPUT_POST, self::$slug . '-nonce' );
     
    636583
    637584        if ( wp_verify_nonce( $nonce, 'run-report' ) && current_user_can( 'manage_network' ) ) {
     585            $error = null;
     586            $range = null;
     587
     588            if ( $start_date || $end_date ) {
     589                try {
     590                    $range = validate_date_range( $start_date, $end_date, [
     591                        'allow_future_start' => true,
     592                        'earliest_start'     => new DateTime( '2006-01-01' ), // No WordCamp posts before 2006.,
     593                    ] );
     594                } catch ( Exception $e ) {
     595                    $error = new WP_Error(
     596                        self::$slug . '-date-range-error',
     597                        $e->getMessage()
     598                    );
     599                }
     600            }
     601
    638602            $include_counts = false;
    639603            if ( ! empty( array_intersect( $fields, [ 'Tickets', 'Speakers', 'Sponsors', 'Organizers' ] ) ) ) {
     
    646610
    647611            $options = array(
    648                 'fields'         => $fields,
    649                 'public'         => false,
    650                 'earliest_start' => new DateTime( '2006-01-01' ), // No WordCamp posts before 2006.
     612                'fields' => $fields,
     613                'public' => false,
    651614            );
    652615
    653             if ( $status ) {
    654                 $options['earliest_start'] = new DateTime( '2015-01-01' ); // No status log data before 2015.
    655             }
    656 
    657             if ( $refresh ) {
    658                 $options['flush_cache'] = true;
    659             }
    660 
    661             $report = new self( $start_date, $end_date, $status, $include_dateless, $include_counts, $options );
     616            $report = new self( $range, [], $include_counts, $options );
    662617
    663618            $filename = [ $report::$name ];
    664             $filename[] = $report->range->start->format( 'Y-m-d' );
    665             $filename[] = $report->range->end->format( 'Y-m-d' );
    666             if ( $report->status ) {
    667                 $filename[] = $report->status;
    668             }
    669             if ( $report->include_dateless ) {
    670                 $filename[] = 'include-dateless';
     619            if ( $report->range instanceof Date_Range ) {
     620                $filename[] = $report->range->start->format( 'Y-m-d' );
     621                $filename[] = $report->range->end->format( 'Y-m-d' );
    671622            }
    672623            if ( $report->include_counts ) {
     
    688639            }
    689640
     641            if ( $error instanceof WP_Error ) {
     642                $exporter->error = $report->merge_errors( $error, $exporter->error );
     643            }
     644
    690645            $exporter->emit_file();
    691646        } // End if().
  • sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/classes/report/class-wordcamp-status.php

    r7516 r7588  
    88
    99use Exception;
    10 use WordCamp\Reports;
    11 use function WordCamp\Reports\Validation\validate_wordcamp_status;
     10use DateTime;
     11use WP_Error, WP_Post;
     12use function WordCamp\Reports\{get_assets_url, get_assets_dir_path, get_views_dir_path};
     13use WordCamp\Reports\Utility\Date_Range;
     14use function WordCamp\Reports\Validation\{validate_date_range, validate_wordcamp_status};
     15use function WordCamp\Reports\Time\{year_array, quarter_array, month_array, convert_time_period_to_date_range};
    1216use WordCamp_Loader;
     17use WordCamp\Utilities\Export_CSV;
    1318
    1419/**
     
    1924 * @package WordCamp\Reports\Report
    2025 */
    21 class WordCamp_Status extends Date_Range {
     26class WordCamp_Status extends Base {
    2227    /**
    2328     * Report name.
     
    6974
    7075    /**
     76     * The date range that defines the scope of the report data.
     77     *
     78     * @var null|Date_Range
     79     */
     80    public $range = null;
     81
     82    /**
    7183     * The status to filter for in the report.
    7284     *
     
    106118        ) );
    107119
    108         parent::__construct( $start_date, $end_date, $options );
     120        parent::__construct( $options );
     121
     122        try {
     123            $this->range = validate_date_range( $start_date, $end_date, $options );
     124        } catch ( Exception $e ) {
     125            $this->error->add(
     126                self::$slug . '-date-error',
     127                $e->getMessage()
     128            );
     129        }
    109130
    110131        if ( $status && 'any' !== $status ) {
     
    139160     */
    140161    protected function get_cache_key() {
    141         $cache_key = parent::get_cache_key();
     162        $cache_key_segments = [
     163            parent::get_cache_key(),
     164            $this->range->generate_cache_key_segment(),
     165        ];
    142166
    143167        if ( $this->status ) {
    144             $cache_key .= '_' . $this->status;
    145         }
    146 
    147         return $cache_key;
     168            $cache_key_segments[] = $this->status;
     169        }
     170
     171        return implode( '_', $cache_key_segments );
     172    }
     173
     174    /**
     175     * Generate a cache expiration interval.
     176     *
     177     * @return int A time interval in seconds.
     178     */
     179    protected function get_cache_expiration() {
     180        return $this->range->generate_cache_duration( parent::get_cache_expiration() );
    148181    }
    149182
     
    177210            // Trim log entries occurring after the date range.
    178211            $logs = array_filter( $logs, function( $entry ) {
    179                 if ( $entry['timestamp'] > $this->end_date->getTimestamp() ) {
     212                if ( $entry['timestamp'] > $this->range->end->getTimestamp() ) {
    180213                    return false;
    181214                }
     
    195228            // Trim log entries occurring before the date range.
    196229            $logs = array_filter( $logs, function( $entry ) {
    197                 if ( $entry['timestamp'] < $this->start_date->getTimestamp() ) {
     230                if ( $entry['timestamp'] < $this->range->start->getTimestamp() ) {
    198231                    return false;
    199232                }
     
    300333                    'key'     => 'Start Date (YYYY-mm-dd)',
    301334                    'compare' => '>=',
    302                     'value'   => strtotime( '-3 months', $this->start_date->getTimestamp() ),
     335                    'value'   => strtotime( '-3 months', $this->range->start->getTimestamp() ),
    303336                    'type'    => 'NUMERIC',
    304337                ),
     
    312345     * Retrieve the log of status changes for a particular WordCamp.
    313346     *
    314      * @param \WP_Post $wordcamp A WordCamp post.
     347     * @param WP_Post $wordcamp A WordCamp post.
    315348     *
    316349     * @return array
    317350     */
    318     protected function get_wordcamp_status_logs( \WP_Post $wordcamp ) {
     351    protected function get_wordcamp_status_logs( WP_Post $wordcamp ) {
    319352        $log_entries = get_post_meta( $wordcamp->ID, '_status_change' );
    320353
     
    398431    public function render_html() {
    399432        $data       = $this->compile_report_data( $this->get_data() );
    400         $start_date = $this->start_date;
    401         $end_date   = $this->end_date;
     433        $start_date = $this->range->start;
     434        $end_date   = $this->range->end;
    402435        $status     = $this->status;
    403436
     
    407440
    408441        if ( ! empty( $this->error->get_error_messages() ) ) {
    409             ?>
    410             <div class="notice notice-error">
    411                 <?php foreach ( $this->error->get_error_messages() as $message ) : ?>
    412                     <?php echo wpautop( wp_kses_post( $message ) ); ?>
    413                 <?php endforeach; ?>
    414             </div>
    415         <?php
     442            $this->render_error_html();
    416443        } else {
    417             include Reports\get_views_dir_path() . 'html/wordcamp-status.php';
     444            include get_views_dir_path() . 'html/wordcamp-status.php';
    418445        }
    419446    }
     
    427454        wp_register_script(
    428455            self::$slug,
    429             Reports\get_assets_url() . 'js/' . self::$slug . '.js',
    430             array( 'jquery' ),
    431             Reports\JS_VERSION,
     456            get_assets_url() . 'js/' . self::$slug . '.js',
     457            array( 'jquery', 'select2' ),
     458            filemtime( get_assets_dir_path() . 'js/' . self::$slug . '.js' ),
    432459            true
    433460        );
     
    435462        wp_register_style(
    436463            self::$slug,
    437             Reports\get_assets_url() . 'css/' . self::$slug . '.css',
    438             array(),
    439             Reports\CSS_VERSION,
     464            get_assets_url() . 'css/' . self::$slug . '.css',
     465            array( 'select2' ),
     466            filemtime( get_assets_dir_path() . 'css/' . self::$slug . '.css' ),
    440467            'screen'
    441468        );
     
    449476    public static function enqueue_admin_assets() {
    450477        self::register_assets();
    451 
     478        WordCamp_Details::register_assets();
     479
     480        wp_enqueue_style( WordCamp_Details::$slug );
    452481        wp_enqueue_script( self::$slug );
    453482        wp_enqueue_style( self::$slug );
     
    468497        $statuses   = WordCamp_Loader::get_post_statuses();
    469498
     499        $field_defaults = [
     500            'ID'                      => 'checked',
     501            'Name'                    => 'checked disabled',
     502            'Start Date (YYYY-mm-dd)' => 'checked',
     503            'End Date (YYYY-mm-dd)'   => 'checked',
     504            'Location'                => 'checked',
     505            'URL'                     => 'checked',
     506            'Created'                 => 'checked',
     507            'Status'                  => 'checked',
     508        ];
     509
    470510        $report = null;
    471511
    472         if ( 'run-report' === $action && wp_verify_nonce( $nonce, 'run-report' ) ) {
     512        if ( 'Show results' === $action
     513             && wp_verify_nonce( $nonce, 'run-report' )
     514             && current_user_can( 'manage_network' )
     515        ) {
    473516            $options = array(
    474                 'earliest_start' => new \DateTime( '2015-01-01' ), // No status log data before 2015.
     517                'public'         => false,
     518                'earliest_start' => new DateTime( '2015-01-01' ), // No status log data before 2015.
    475519            );
    476520
     
    480524
    481525            $report = new self( $start_date, $end_date, $status, $options );
    482 
    483             // The report adjusts the end date in some circumstances.
    484             if ( empty( $report->error->get_error_messages() ) ) {
    485                 $end_date = $report->end_date->format( 'Y-m-d' );
    486             }
    487         }
    488 
    489         include Reports\get_views_dir_path() . 'report/wordcamp-status.php';
     526        }
     527
     528        include get_views_dir_path() . 'report/wordcamp-status.php';
     529    }
     530
     531    /**
     532     * Export the report data to a file.
     533     *
     534     * @return void
     535     */
     536    public static function export_to_file() {
     537        $start_date = filter_input( INPUT_POST, 'start-date' );
     538        $end_date   = filter_input( INPUT_POST, 'end-date' );
     539        $status     = filter_input( INPUT_POST, 'status' );
     540        $fields     = filter_input( INPUT_POST, 'fields', FILTER_SANITIZE_STRING, [ 'flags' => FILTER_REQUIRE_ARRAY ] );
     541        $refresh    = filter_input( INPUT_POST, 'refresh', FILTER_VALIDATE_BOOLEAN );
     542        $action     = filter_input( INPUT_POST, 'action' );
     543        $nonce      = filter_input( INPUT_POST, self::$slug . '-nonce' );
     544
     545        if ( 'Export CSV' !== $action ) {
     546            return;
     547        }
     548
     549        if ( wp_verify_nonce( $nonce, 'run-report' ) && current_user_can( 'manage_network' ) ) {
     550            $error = null;
     551
     552            $options = array(
     553                'public'         => false,
     554                'earliest_start' => new DateTime( '2015-01-01' ), // No status log data before 2015.
     555            );
     556
     557            if ( $refresh ) {
     558                $options['flush_cache'] = true;
     559            }
     560
     561            $status_report = new self( $start_date, $end_date, $status, $options );
     562            $wordcamp_ids  = array_keys( $status_report->get_data() );
     563
     564            if ( ! empty( $status_report->error->get_error_messages() ) ) {
     565                $error = $status_report->error;
     566            } elseif ( empty( $wordcamp_ids ) ) {
     567                $error = new WP_Error(
     568                    self::$slug . '-export-error',
     569                    'No status data available for the given criteria.'
     570                );
     571            }
     572
     573            $include_counts = false;
     574            if ( ! empty( array_intersect( $fields, [ 'Tickets', 'Speakers', 'Sponsors', 'Organizers' ] ) ) ) {
     575                $include_counts = true;
     576            }
     577
     578            // The "Name" field should always be included, but does not get submitted because the input is disabled,
     579            // so add it in here.
     580            $fields[] = 'Name';
     581
     582            $options = array(
     583                'fields' => $fields,
     584                'public' => false,
     585            );
     586
     587            $details_report = new WordCamp_Details( null, $wordcamp_ids, $include_counts, $options );
     588
     589            $filename = [ $status_report::$name ];
     590            $filename[] = $status_report->range->start->format( 'Y-m-d' );
     591            $filename[] = $status_report->range->end->format( 'Y-m-d' );
     592            if ( $status_report->status ) {
     593                $filename[] = $status_report->status;
     594            }
     595            if ( $details_report->include_counts ) {
     596                $filename[] = 'include-counts';
     597            }
     598
     599            $data = $details_report->prepare_data_for_display( $details_report->get_data() );
     600
     601            $headers = ( ! empty( $data ) ) ? array_keys( $data[0] ) : [];
     602
     603            $exporter = new Export_CSV( array(
     604                'filename' => $filename,
     605                'headers'  => $headers,
     606                'data'     => $data,
     607            ) );
     608
     609            if ( ! empty( $details_report->error->get_error_messages() ) ) {
     610                $exporter->error = $details_report->merge_errors( $details_report->error, $exporter->error );
     611            }
     612
     613            if ( $error instanceof WP_Error ) {
     614                $exporter->error = $details_report->merge_errors( $error, $exporter->error );
     615            }
     616
     617            $exporter->emit_file();
     618        } // End if().
    490619    }
    491620
     
    503632            self::register_assets();
    504633
     634            wp_enqueue_style( 'select2' );
    505635            wp_enqueue_script( self::$slug );
    506636
     
    525655        $action = filter_input( INPUT_GET, 'action' );
    526656
    527         $years    = self::year_array( absint( date( 'Y' ) ), 2015 );
    528         $months   = self::month_array();
     657        $years    = year_array( absint( date( 'Y' ) ), 2015 );
     658        $quarters = quarter_array();
     659        $months   = month_array();
    529660        $statuses = WordCamp_Loader::get_post_statuses();
    530661
     
    540671
    541672        if ( 'Show results' === $action ) {
    542             $range = self::convert_time_period_to_date_range( $year, $period );
     673            try {
     674                $range = convert_time_period_to_date_range( $year, $period );
     675            } catch ( Exception $e ) {
     676                $error = new WP_Error(
     677                    self::$slug . '-time-period-error',
     678                    $e->getMessage()
     679                );
     680            }
    543681
    544682            $options = array(
    545                 'earliest_start' => new \DateTime( '2015-01-01' ), // No status log data before 2015.
     683                'earliest_start' => new DateTime( '2015-01-01' ), // No status log data before 2015.
    546684            );
    547685
    548             $report = new self( $range['start_date'], $range['end_date'], $status, $options );
    549         }
    550 
    551         include Reports\get_views_dir_path() . 'public/wordcamp-status.php';
     686            $report = new self( $range->start, $range->end, $status, $options );
     687        }
     688
     689        include get_views_dir_path() . 'public/wordcamp-status.php';
    552690    }
    553691}
  • sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/includes/validation.php

    r7573 r7588  
    55
    66use Exception;
     7use stdClass;
    78use DateTime, DateTimeImmutable, DateInterval;
    89use WP_Post;
     
    121122 * }
    122123 *
    123  * @return array An associative array containing valid post ID and site ID integers for the WordCamp.
     124 * @return object An object containing properties for valid post ID and site ID integers for the WordCamp.
    124125 * @throws Exception
    125126 */
     
    140141
    141142    if ( ! $wordcamp_post instanceof WP_Post || WCPT_POST_TYPE_ID !== get_post_type( $wordcamp_post ) ) {
    142         throw new Exception( 'Please enter a valid WordCamp ID.' );
    143     }
    144 
    145     $valid = [
    146         'post_id' => $post_id,
    147         'site_id' => 0,
    148     ];
    149 
    150     if ( $config['require_site'] ) {
    151         $site_id = get_wordcamp_site_id( $wordcamp_post );
    152 
    153         if ( ! $site_id ) {
    154             throw new Exception( 'The specified WordCamp does not have a site yet.' );
    155         }
    156 
    157         $valid['site_id'] = $site_id;
    158     }
     143        throw new Exception( sprintf(
     144            'Invalid WordCamp ID: %s',
     145            esc_html( $post_id )
     146        ) );
     147    }
     148
     149    $valid = new stdClass();
     150
     151    $valid->post_id = $post_id;
     152    $valid->site_id = get_wordcamp_site_id( $wordcamp_post );
    159153
    160154    if ( $switched ) {
    161155        restore_current_blog();
     156    }
     157
     158    if ( $config['require_site'] && ! $valid->site_id ) {
     159        throw new Exception( sprintf(
     160            'The WordCamp with ID %d does not have a site.',
     161            absint( $post_id )
     162        ) );
    162163    }
    163164
  • sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/index.php

    r7573 r7588  
    134134        __NAMESPACE__ . '\Report\Payment_Activity',
    135135        __NAMESPACE__ . '\Report\Sponsorship_Grants',
     136        __NAMESPACE__ . '\Report\WordCamp_Status',
    136137        __NAMESPACE__ . '\Report\WordCamp_Details',
    137         __NAMESPACE__ . '\Report\WordCamp_Status',
    138138        __NAMESPACE__ . '\Report\Meetup_Groups',
    139139        __NAMESPACE__ . '\Report\Meetup_Events',
  • sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/views/html/wordcamp-status.php

    r6662 r7588  
    77defined( 'WPINC' ) || die();
    88
    9 /** @var \DateTime $start_date */
    10 /** @var \DateTime $end_date */
     9use DateTime;
     10
     11/** @var DateTime $start_date */
     12/** @var DateTime $end_date */
    1113/** @var string $status */
    1214/** @var array $active_camps */
     
    1517?>
    1618
    17 <?php if ( ! empty( $active_camps ) ) : ?>
     19<?php if ( count( $active_camps ) ) : ?>
    1820    <h3 id="active-heading">
    1921        <?php if ( $status ) : ?>
     
    2830        <?php endif; ?>
    2931    </h3>
     32
     33    <table class="striped widefat but-not-too-wide">
     34        <tr>
     35            <td>Active WordCamps</td>
     36            <td class="number"><?php echo number_format_i18n( count( $active_camps ) ); ?></td>
     37        </tr>
     38    </table>
    3039
    3140    <?php foreach ( $active_camps as $active_camp ) : ?>
     
    4352<?php endif; ?>
    4453
    45 <?php if ( ! empty( $inactive_camps ) ) : ?>
     54<?php if ( count( $inactive_camps ) ) : ?>
    4655    <h3 id="inactive-heading">
    4756        WordCamps
     
    5766    </h3>
    5867
     68    <table class="striped widefat but-not-too-wide">
     69        <tr>
     70            <td>Inactive WordCamps</td>
     71            <td class="number"><?php echo number_format_i18n( count( $inactive_camps ) ); ?></td>
     72        </tr>
     73    </table>
     74
    5975    <ul>
    6076    <?php foreach ( $inactive_camps as $inactive_camp ) : ?>
     
    6985
    7086<?php if ( empty( $active_camps ) && empty( $inactive_camps ) ) : ?>
    71     <h3 id="no-data-heading">
     87    <p>
    7288        No data
    7389        <?php if ( $status ) : ?>
     
    7995            between <?php echo esc_html( $start_date->format( 'M jS, Y' ) ); ?> and <?php echo esc_html( $end_date->format( 'M jS, Y' ) ); ?>
    8096        <?php endif; ?>
    81     </h3>
     97    </p>
    8298<?php endif; ?>
  • sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/views/public/wordcamp-status.php

    r6662 r7588  
    1313/** @var string $status */
    1414/** @var array  $years */
     15/** @var array  $quarters */
    1516/** @var array  $months */
    1617/** @var array  $statuses */
     
    3738            <select id="period" name="period">
    3839                <option value="all"<?php selected( 'all' === $period ); ?>>Entire year</option>
    39                 <option value="q1"<?php selected( 'q1' === $period ); ?>>1st quarter</option>
    40                 <option value="q2"<?php selected( 'q2' === $period ); ?>>2nd quarter</option>
    41                 <option value="q3"<?php selected( 'q3' === $period ); ?>>3rd quarter</option>
    42                 <option value="q4"<?php selected( 'q4' === $period ); ?>>4th quarter</option>
     40                <?php foreach ( $quarters as $quarter_value => $quarter_label ) : ?>
     41                    <option value="<?php echo esc_attr( $quarter_value ); ?>"<?php selected( $quarter_value, $period ); ?>><?php echo esc_html( $quarter_label ); ?></option>
     42                <?php endforeach; ?>
    4343                <?php foreach ( $months as $month_value => $month_label ) : ?>
    4444                    <option value="<?php echo esc_attr( $month_value ); ?>"<?php selected( $month_value, $period ); ?>><?php echo esc_html( $month_label ); ?></option>
  • sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/views/report/wordcamp-details.php

    r7550 r7588  
    1010use WordCamp\Reports\Report;
    1111
    12 /** @var string $start_date */
    13 /** @var string $end_date */
    14 /** @var bool   $include_dateless */
    15 /** @var string $status */
    16 /** @var array  $statuses */
    17 /** @var array  $available_fields */
     12/** @var array $field_defaults */
    1813?>
    1914
     
    3833            <tbody>
    3934            <tr>
    40                 <th scope="row"><label for="start-date">Start Date</label></th>
    41                 <td><input type="date" id="start-date" name="start-date" value="<?php echo esc_attr( $start_date ) ?>" required /></td>
     35                <th scope="row"><label for="start-date">Start Date (optional)</label></th>
     36                <td><input type="date" id="start-date" name="start-date" value="" /></td>
    4237            </tr>
    4338            <tr>
    44                 <th scope="row"><label for="end-date">End Date</label></th>
    45                 <td><input type="date" id="end-date" name="end-date" value="<?php echo esc_attr( $end_date ) ?>" required /></td>
    46             </tr>
    47             <tr>
    48                 <th scope="row"><label for="include_dateless">Include WordCamps without a date</label></th>
    49                 <td><input type="checkbox" id="include_dateless" name="include_dateless"<?php checked( $include_dateless ); ?> /></td>
    50             </tr>
    51             <tr>
    52                 <th scope="row"><label for="status">Status (optional)</label></th>
    53                 <td>
    54                     <select id="status" name="status">
    55                         <option value="any"<?php selected( ( ! $status || 'any' === $status ) ); ?>>Any</option>
    56                         <?php foreach ( $statuses as $value => $label ) : ?>
    57                             <option value="<?php echo esc_attr( $value ); ?>"<?php selected( $value, $status ); ?>><?php echo esc_attr( $label ); ?></option>
    58                         <?php endforeach; ?>
    59                     </select>
    60                 </td>
    61             </tr>
    62             <tr>
    63                 <th scope="row"><label for="refresh">Refresh results</label></th>
    64                 <td><input type="checkbox" id="refresh" name="refresh" /></td>
     39                <th scope="row"><label for="end-date">End Date (optional)</label></th>
     40                <td><input type="date" id="end-date" name="end-date" value="" /></td>
    6541            </tr>
    6642            </tbody>
    6743        </table>
    6844
    69         <fieldset class="fields-container">
    70             <legend class="fields-label">Available Fields</legend>
    71 
    72             <?php foreach ( $available_fields as $field_name => $extra_props ) : ?>
    73                 <div class="field-checkbox">
    74                     <input
    75                         type="checkbox"
    76                         id="fields-<?php echo esc_attr( $field_name ); ?>"
    77                         name="fields[]"
    78                         value="<?php echo esc_attr( $field_name ); ?>"
    79                         <?php if ( $extra_props && is_string( $extra_props ) ) echo esc_html( $extra_props ); ?>
    80                     />
    81                     <label for="fields-<?php echo esc_attr( $field_name ); ?>">
    82                         <?php echo esc_attr( $field_name ); ?>
    83                     </label>
    84                 </div>
    85             <?php endforeach; ?>
    86         </fieldset>
     45        <?php Report\WordCamp_Details::render_available_fields( 'private', $field_defaults ) ?>
    8746
    8847        <?php submit_button( 'Export CSV', 'primary', 'action', false ); ?>
  • sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-reports/views/report/wordcamp-status.php

    r7434 r7588  
    1414/** @var string $status */
    1515/** @var array  $statuses */
     16/** @var array  $field_defaults */
    1617/** @var Report\WordCamp_Status|null $report */
    1718?>
     
    3839                <tr>
    3940                    <th scope="row"><label for="start-date">Start Date</label></th>
    40                     <td><input type="date" id="start-date" name="start-date" value="<?php echo esc_attr( $start_date ) ?>" /></td>
     41                    <td><input type="date" id="start-date" name="start-date" value="<?php echo esc_attr( $start_date ) ?>" required /></td>
    4142                </tr>
    4243                <tr>
    4344                    <th scope="row"><label for="end-date">End Date</label></th>
    44                     <td><input type="date" id="end-date" name="end-date" value="<?php echo esc_attr( $end_date ) ?>" /></td>
     45                    <td><input type="date" id="end-date" name="end-date" value="<?php echo esc_attr( $end_date ) ?>" required /></td>
    4546                </tr>
    4647                <tr>
    4748                    <th scope="row"><label for="status">Status (optional)</label></th>
    4849                    <td>
    49                         <select id="status" name="status">
     50                        <select id="status" name="status" class="select2-container">
    5051                            <option value="any"<?php selected( ( ! $status || 'any' === $status ) ); ?>>Any</option>
    5152                            <?php foreach ( $statuses as $value => $label ) : ?>
     
    5354                            <?php endforeach; ?>
    5455                        </select>
     56                        <p class="description">
     57                            This will select all WordCamps that had this status at any time during the date range.
     58                        </p>
    5559                    </td>
    5660                </tr>
     
    6266        </table>
    6367
    64         <?php submit_button( 'Show results', 'primary', '' ); ?>
     68        <?php submit_button( 'Show results', 'primary', 'action', false ); ?>
     69
     70        <button id="fields-toggle" class="secondary button-secondary">Export resulting WordCamps as a CSV</button>
     71
     72        <section id="fields-section" class="hidden">
     73            <?php Report\WordCamp_Details::render_available_fields( 'private', $field_defaults ); ?>
     74            <?php submit_button( 'Export CSV', 'primary', 'action', false ); ?>
     75        </section>
    6576    </form>
    6677
Note: See TracChangeset for help on using the changeset viewer.