Making WordPress.org

Changeset 7588


Ignore:
Timestamp:
08/02/2018 07:12:19 PM (8 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.