Making WordPress.org

Ticket #2955: 2955.2.diff

File 2955.2.diff, 59.5 KB (added by pierlo, 4 years ago)
  • new file wordpress.org/public_html/wp-content/plugins/wporg-meeting-ical/inc/class-ical-generator.php

    diff --git wordpress.org/public_html/wp-content/plugins/wporg-meeting-ical/inc/class-ical-generator.php wordpress.org/public_html/wp-content/plugins/wporg-meeting-ical/inc/class-ical-generator.php
    new file mode 100644
    index 000000000..bcedf3444
    - +  
     1<?php
     2
     3namespace WordPressdotorg\Meetings\Calendar;
     4
     5class ICAL_Generator {
     6
     7
     8        const NEWLINE = "\r\n";
     9
     10        /**
     11         * Generate an iCalendar for the given set of meetings.
     12         *
     13         * @param WP_Post[] $posts
     14         * @return string
     15         */
     16        public function generate( $posts ) {
     17                $ical  = 'BEGIN:VCALENDAR' . self::NEWLINE;
     18                $ical .= 'VERSION:2.0' . self::NEWLINE;
     19                $ical .= 'PRODID:-//WPORG Make//Meeting Events Calendar//EN' . self::NEWLINE;
     20                $ical .= 'METHOD:PUBLISH' . self::NEWLINE;
     21                $ical .= 'CALSCALE:GREGORIAN' . self::NEWLINE;
     22
     23                foreach ( $posts as $post ) {
     24                        $ical .= $this->generate_event( $post );
     25                }
     26
     27                $ical .= 'END:VCALENDAR';
     28                return $ical;
     29        }
     30
     31        /**
     32         * Generate an event for a meeting.
     33         *
     34         * @param WP_Post $post
     35         * @return string
     36         */
     37        private function generate_event( $post ) {
     38                $id        = $post->ID;
     39                $title     = $post->post_title;
     40                $location  = $post->location;
     41                $link      = $post->link;
     42                $team      = $post->team;
     43                $recurring = $post->recurring;
     44                $sequence  = empty( $post->sequence ) ? 0 : intval( $post->sequence );
     45
     46                $start_date      = strftime( '%Y%m%d', strtotime( $post->next_date ) );
     47                $start_time      = strftime( '%H%M%S', strtotime( $post->time ) );
     48                $start_date_time = "{$start_date}T{$start_time}Z";
     49
     50                $end_date      = $start_date;
     51                $end_time      = strftime( '%H%M%S', strtotime( "{$post->time} +1 hour" ) );
     52                $end_date_time = "{$end_date}T{$end_time}Z";
     53
     54                $description   = '';
     55                $slack_channel = null;
     56
     57                if ( $location && preg_match( '/^#([-\w]+)$/', trim( $location ), $match ) ) {
     58                        $slack_channel = '#' . sanitize_title( $match[1] );
     59                        $location      = "{$slack_channel} channel on Slack";
     60                }
     61
     62                if ( $link ) {
     63                        if ( $slack_channel ) {
     64                                $description .= "Slack channel link: https://wordpress.slack.com/messages/{$slack_channel}\\n";
     65                        }
     66
     67                        $description .= "For more information visit {$link}";
     68                }
     69
     70                $frequency = $this->get_frequency( $recurring, $post->next_date, $post->occurrence );
     71
     72                $event  = 'BEGIN:VEVENT' . self::NEWLINE;
     73                $event .= "UID:{$id}" . self::NEWLINE;
     74
     75                $event .= "DTSTAMP:{$start_date_time}" . self::NEWLINE;
     76                $event .= "DTSTART;VALUE=DATE:{$start_date_time}" . self::NEWLINE;
     77                $event .= "DTEND;VALUE=DATE:{$end_date_time}" . self::NEWLINE;
     78                $event .= 'CATEGORIES:WordPress' . self::NEWLINE;
     79                // Some calendars require the organizer's name and email address
     80                $event .= "ORGANIZER;CN=WordPress {$team} Team:mailto:mail@example.com" . self::NEWLINE;
     81                $event .= "SUMMARY:{$team}: {$title}" . self::NEWLINE;
     82                // Incrementing the sequence number updates the specified event
     83                $event .= "SEQUENCE:{$sequence}" . self::NEWLINE;
     84                $event .= 'STATUS:CONFIRMED' . self::NEWLINE;
     85                $event .= 'TRANSP:OPAQUE' . self::NEWLINE;
     86
     87                if ( ! empty( $location ) ) {
     88                        $event .= "LOCATION:{$location}" . self::NEWLINE;
     89                }
     90
     91                if ( ! empty( $description ) ) {
     92                        $event .= "DESCRIPTION:{$description}" . self::NEWLINE;
     93                }
     94
     95                if ( ! is_null( $frequency ) ) {
     96                        $event .= "RRULE:FREQ={$frequency}" . self::NEWLINE;
     97                }
     98
     99                $event .= 'END:VEVENT' . self::NEWLINE;
     100
     101                return $event;
     102        }
     103
     104        private function get_frequency( $recurrence, $date, $occurrences ) {
     105                switch ( $recurrence ) {
     106                        case 'weekly':
     107                                $frequency = 'WEEKLY';
     108                                break;
     109                        case 'biweekly':
     110                                $frequency = 'WEEKLY;INTERVAL=2';
     111                                break;
     112                        case 'monthly':
     113                                $frequency = 'MONTHLY';
     114                                break;
     115                        case 'occurrence':
     116                                $frequency = $this->get_frequencies_by_day( $occurrences, $date );
     117                                break;
     118                        default:
     119                                $frequency = null;
     120                }
     121
     122                return $frequency;
     123        }
     124
     125        /**
     126         * Returns a comma separated list of days in which the event should repeat for the month.
     127         *
     128         * For example, given:
     129         *   $occurrences = array( 1, 3 ) // 1st and 3rd week in the month
     130         *   $date = '2019-09-15' // the day is Sunday
     131         * it will return: 'MONTHLY;BYDAY=1SU,3SU'
     132         *
     133         * @param array $occurrences
     134         * @param string $date
     135         * @return string
     136         */
     137        private function get_frequencies_by_day( $occurrences, $date ) {
     138                // Get the first two letters of the day of the start date in uppercase letters
     139                $day = strtoupper(
     140                        substr( strftime( '%a', strtotime( $date ) ), 0, 2 )
     141                );
     142
     143                $by_days = array_reduce(
     144                        array_keys( $occurrences ),
     145                        function ( $carry, $key ) use ( $day, $occurrences ) {
     146                                $carry .= $occurrences[ $key ] . $day;
     147
     148                                if ( $key < count( $occurrences ) - 1 ) {
     149                                        $carry .= ',';
     150                                }
     151
     152                                return $carry;
     153                        }
     154                );
     155
     156                return "MONTHLY;BYDAY={$by_days}";
     157        }
     158}
  • new file wordpress.org/public_html/wp-content/plugins/wporg-meeting-ical/inc/class-plugin.php

    diff --git wordpress.org/public_html/wp-content/plugins/wporg-meeting-ical/inc/class-plugin.php wordpress.org/public_html/wp-content/plugins/wporg-meeting-ical/inc/class-plugin.php
    new file mode 100644
    index 000000000..09253d6b0
    - +  
     1<?php
     2
     3namespace WordPressdotorg\Meetings\Calendar;
     4
     5use WordPressdotorg\Meetings\Common\Meeting_Query;
     6
     7class Plugin {
     8
     9        const QUERY_KEY      = 'meeting_ical';
     10        const QUERY_TEAM_KEY = 'meeting_team';
     11
     12        /**
     13         * @var Plugin The singleton instance.
     14         */
     15        private static $instance;
     16
     17        /**
     18         * Returns always the same instance of this plugin.
     19         *
     20         * @return Plugin
     21         */
     22        public static function get_instance() {
     23                if ( ! ( self::$instance instanceof Plugin ) ) {
     24                        self::$instance = new Plugin();
     25                }
     26                return self::$instance;
     27        }
     28
     29        /**
     30         * Instantiates a new Plugin object.
     31         */
     32        private function __construct() {
     33                add_action( 'plugins_loaded', array( $this, 'plugins_loaded' ) );
     34        }
     35
     36        public function plugins_loaded() {
     37                // Stop loading if "Meeting Post Type" plugin not available.
     38                if ( ! class_exists( '\WordPressdotorg\Meetings\PostType\Plugin' ) ) {
     39                        add_action(
     40                                'admin_notices',
     41                                function () {
     42                                        echo '<div class="error"><p><strong>' . 'The Meetings iCalendar API requires the "Meetings Post Type" plugin to be installed and active' . '</strong></p></div>';
     43                                }
     44                        );
     45
     46                        return;
     47                }
     48
     49                register_activation_hook( __FILE__, array( $this, 'on_activate' ) );
     50                register_deactivation_hook( __FILE__, array( $this, 'on_deactivate' ) );
     51
     52                add_action( 'init', array( $this, 'add_rewrite_rules' ) );
     53                add_action( 'parse_request', array( $this, 'parse_request' ) );
     54
     55                add_filter( 'query_vars', array( $this, 'query_vars' ) );
     56        }
     57
     58        public function on_activate() {
     59                $this->add_rewrite_rules();
     60                flush_rewrite_rules();
     61        }
     62
     63        public function on_deactivate() {
     64                flush_rewrite_rules(); // remove custom rewrite rule
     65                delete_option( self::QUERY_KEY ); // remove cache
     66        }
     67
     68        public function add_rewrite_rules() {
     69                add_rewrite_rule(
     70                        '^meetings/?([a-zA-Z\d\s_-]+)?/calendar\.ics$',
     71                        array(
     72                                self::QUERY_KEY      => 1,
     73                                self::QUERY_TEAM_KEY => '$matches[1]',
     74                        ),
     75                        'top'
     76                );
     77        }
     78
     79        public function parse_request( $request ) {
     80                if ( ! array_key_exists( self::QUERY_KEY, $request->query_vars ) ) {
     81                        return;
     82                }
     83
     84                $team = strtolower( $request->query_vars[ self::QUERY_TEAM_KEY ] );
     85
     86                // Generate a calendar if such a team exists
     87                $ical = $this->get_ical_contents( $team );
     88
     89                if ( null !== $ical ) {
     90                        /**
     91                         * If the calendar has a 'method' property, the 'Content-Type' header must also specify it
     92                         */
     93                        header( 'Content-Type: text/calendar; charset=utf-8; method=publish' );
     94                        header( 'Content-Disposition: inline; filename=calendar.ics' );
     95                        echo $ical;
     96                        exit;
     97                }
     98
     99                return;
     100        }
     101
     102        public function query_vars( $query_vars ) {
     103                array_push( $query_vars, self::QUERY_KEY );
     104                array_push( $query_vars, self::QUERY_TEAM_KEY );
     105                return $query_vars;
     106        }
     107
     108        private function get_ical_contents( $team ) {
     109                $ttl    = 1; // in seconds
     110                $option = $team ? self::QUERY_KEY . "_{$team}" : self::QUERY_KEY;
     111                $cache  = get_option( $option, false );
     112
     113                if ( is_array( $cache ) && $cache['timestamp'] > time() - $ttl ) {
     114                        return $cache['contents'];
     115                }
     116
     117                $contents = $this->generate_ical_contents( $team );
     118
     119                if ( null !== $contents ) {
     120                        $cache = array(
     121                                'contents'  => $contents,
     122                                'timestamp' => time(),
     123                        );
     124                        delete_option( $option );
     125                        add_option( $option, $cache, false, false );
     126
     127                        return $cache['contents'];
     128                }
     129
     130                return null;
     131        }
     132
     133        private function generate_ical_contents( $team ) {
     134                $posts = $this->get_meeting_posts( $team );
     135
     136                // Don't generate a calendar if there are no meetings for that team
     137                if ( empty( $posts ) ) {
     138                        return null;
     139                }
     140
     141                $ical_generator = new ICAL_Generator();
     142                return $ical_generator->generate( $posts );
     143        }
     144
     145        /**
     146         * Get all meetings for a team. If the 'team' parameter is empty, all meetings are returned.
     147         *
     148         * @param string $team Name of the team to fetch meetings for.
     149         * @return array
     150         */
     151        private function get_meeting_posts( $team = '' ) {
     152                $query = new Meeting_Query( $team );
     153
     154                return $query->get_posts();
     155        }
     156}
     157
  • new file wordpress.org/public_html/wp-content/plugins/wporg-meeting-ical/vendor/wordpressdotorg/autoload/class-autoloader.php

    diff --git wordpress.org/public_html/wp-content/plugins/wporg-meeting-ical/vendor/wordpressdotorg/autoload/class-autoloader.php wordpress.org/public_html/wp-content/plugins/wporg-meeting-ical/vendor/wordpressdotorg/autoload/class-autoloader.php
    new file mode 100644
    index 000000000..8c2258ba3
    - +  
     1<?php
     2namespace WordPressdotorg\Autoload;
     3
     4/**
     5 * An Autoloader which respects WordPress's filename standards.
     6 *
     7 * @package WordPressdotorg\Autoload
     8 */
     9class Autoloader {
     10
     11        /**
     12         * Namespace separator.
     13         */
     14        const NS_SEPARATOR = '\\';
     15
     16        /**
     17         * The prefix to compare classes against.
     18         *
     19         * @var string
     20         * @access protected
     21         */
     22        protected $prefix;
     23
     24        /**
     25         * Length of the prefix string.
     26         *
     27         * @var int
     28         * @access protected
     29         */
     30        protected $prefix_length;
     31
     32        /**
     33         * Path to the file to be loaded.
     34         *
     35         * @var string
     36         * @access protected
     37         */
     38        protected $path;
     39
     40        /**
     41         * Constructor.
     42         *
     43         * @param string $prefix Prefix all classes have in common.
     44         * @param string $path   Path to the files to be loaded.
     45         */
     46        public function __construct( $prefix, $path ) {
     47                $this->prefix        = $prefix;
     48                $this->prefix_length = strlen( $prefix );
     49                $this->path          = trailingslashit( $path );
     50        }
     51
     52        /**
     53         * Loads a class if it starts with `$this->prefix`.
     54         *
     55         * @param string $class The class to be loaded.
     56         */
     57        public function load( $class ) {
     58                if ( strpos( $class, $this->prefix . self::NS_SEPARATOR ) !== 0 ) {
     59                        return;
     60                }
     61
     62                // Strip prefix from the start (ala PSR-4)
     63                $class = substr( $class, $this->prefix_length + 1 );
     64                $class = strtolower( $class );
     65                $file  = '';
     66
     67                if ( false !== ( $last_ns_pos = strripos( $class, self::NS_SEPARATOR ) ) ) {
     68                        $namespace = substr( $class, 0, $last_ns_pos );
     69                        $namespace = str_replace( '_', '-', $namespace );
     70                        $class     = substr( $class, $last_ns_pos + 1 );
     71                        $file      = str_replace( self::NS_SEPARATOR, DIRECTORY_SEPARATOR, $namespace ) . DIRECTORY_SEPARATOR;
     72                }
     73
     74                $file .= 'class-' . str_replace( '_', '-', $class ) . '.php';
     75
     76                $path = $this->path . $file;
     77
     78                if ( file_exists( $path ) ) {
     79                        require $path;
     80                }
     81        }
     82}
     83
     84/**
     85 * Registers Autoloader's autoload function.
     86 *
     87 * @param string $prefix
     88 * @param string $path
     89 */
     90function register_class_path( $prefix, $path ) {
     91        $loader = new Autoloader( $prefix, $path );
     92        spl_autoload_register( array( $loader, 'load' ) );
     93}
  • new file wordpress.org/public_html/wp-content/plugins/wporg-meeting-ical/wporg-meeting-ical.php

    diff --git wordpress.org/public_html/wp-content/plugins/wporg-meeting-ical/wporg-meeting-ical.php wordpress.org/public_html/wp-content/plugins/wporg-meeting-ical/wporg-meeting-ical.php
    new file mode 100644
    index 000000000..507a38f6d
    - +  
     1<?php
     2/*
     3Plugin Name: WPORG Make Homepage Meeting iCalendar API
     4Description: Provides API endpoints for iCalendar files that are generated dynamically from WPORG Meetings
     5Version:     1.0.0
     6License:     GPLv2 or later
     7Author:      WordPress.org
     8Author URI:  http://wordpress.org/
     9Text Domain: wporg
     10*/
     11
     12namespace WordPressdotorg\Meetings\Calendar;
     13
     14use WordPressdotorg\Autoload;
     15
     16if ( ! class_exists( '\WordPressdotorg\Autoload\Autoloader', false ) ) {
     17        include __DIR__ . '/vendor/wordpressdotorg/autoload/class-autoloader.php';
     18}
     19
     20// Register an Autoloader for all files.
     21Autoload\register_class_path( __NAMESPACE__, __DIR__ . '/inc' );
     22
     23// Instantiate the Plugin.
     24Plugin::get_instance();
  • new file wordpress.org/public_html/wp-content/plugins/wporg-meeting-posttype/inc/class-plugin.php

    diff --git wordpress.org/public_html/wp-content/plugins/wporg-meeting-posttype/inc/class-plugin.php wordpress.org/public_html/wp-content/plugins/wporg-meeting-posttype/inc/class-plugin.php
    new file mode 100644
    index 000000000..496ddf41f
    - +  
     1<?php
     2
     3
     4namespace WordPressdotorg\Meetings\PostType;
     5
     6use DateTime;
     7use Exception;
     8use WordPressdotorg\Meetings\Common\Meeting_Query;
     9
     10class Plugin {
     11
     12        /**
     13         * @var Plugin The singleton instance.
     14         */
     15        private static $instance;
     16
     17        /**
     18         * Returns always the same instance of this plugin.
     19         *
     20         * @return Plugin
     21         */
     22        public static function get_instance() {
     23                if ( ! ( self::$instance instanceof Plugin ) ) {
     24                        self::$instance = new Plugin();
     25                }
     26                return self::$instance;
     27        }
     28
     29        /**
     30         * Instantiates a new Plugin object.
     31         */
     32        private function __construct() {
     33                add_action( 'plugins_loaded', array( $this, 'plugins_loaded' ) );
     34        }
     35
     36        public function plugins_loaded() {
     37                add_action( 'init', array( $this, 'register_meeting_post_type' ) );
     38                add_action( 'save_post_meeting', array( $this, 'save_meta_boxes' ), 10, 2 );
     39                add_filter( 'pre_get_posts', array( $this, 'meeting_archive_page_query' ) );
     40                add_filter( 'the_posts', array( $this, 'meeting_set_next_meeting' ), 10, 2 );
     41                add_filter( 'manage_meeting_posts_columns', array( $this, 'meeting_add_custom_columns' ) );
     42                add_action( 'manage_meeting_posts_custom_column', array( $this, 'meeting_custom_columns' ), 10, 2 );
     43                add_action( 'admin_head', array( $this, 'meeting_column_width' ) );
     44                add_action( 'admin_bar_menu', array( $this, 'add_edit_meetings_item_to_admin_bar' ), 80 );
     45                add_action( 'wp_enqueue_scripts', array( $this, 'add_edit_meetings_icon_to_admin_bar' ) );
     46                add_shortcode( 'meeting_time', array( $this, 'meeting_time_shortcode' ) );
     47        }
     48
     49        public function meeting_column_width() { ?>
     50                <style type="text/css">
     51                        .column-team { width: 10em !important; overflow: hidden; }
     52                        #meeting-info .recurring label { padding-right: 10px; }
     53                </style>
     54                <?php
     55        }
     56
     57        public function meeting_add_custom_columns( $columns ) {
     58                $columns = array_slice( $columns, 0, 1, true )
     59                        + array( 'team' => __( 'Team', 'wporg' ) )
     60                        + array_slice( $columns, 1, null, true );
     61                return $columns;
     62        }
     63
     64        public function meeting_custom_columns( $column, $post_id ) {
     65                switch ( $column ) {
     66                        case 'team':
     67                                $team = get_post_meta( $post_id, 'team', true );
     68                                echo esc_html( $team );
     69                                break;
     70                }
     71        }
     72
     73        public function meeting_archive_page_query( $query ) {
     74                if ( is_admin() || ! $query->is_main_query() || ! $query->is_post_type_archive( 'meeting' ) ) {
     75                        return;
     76                }
     77                // turn off paging on the archive page, to show all meetings in the table
     78                $query->set( 'nopaging', true );
     79
     80                // meta query to eliminate expired meetings from query
     81                $query->set( 'meta_query', Meeting_Query::get_meta_query() );
     82
     83                // WP doesn't understand CURDATE() and prepares it as a quoted string. Repair this:
     84                add_filter(
     85                        'get_meta_sql',
     86                        function ( $sql ) {
     87                                return str_replace( "'CURDATE()'", 'CURDATE()', $sql );
     88                        }
     89                );
     90
     91        }
     92
     93        public function meeting_set_next_meeting( $posts, $query ) {
     94                if ( ! $query->is_post_type_archive( 'meeting' ) ) {
     95                        return $posts;
     96                }
     97
     98                // for each entry, set a fake meta value to show the next date for recurring meetings
     99                array_walk(
     100                        $posts,
     101                        function ( &$post ) {
     102                                if ( 'weekly' === $post->recurring || '1' === $post->recurring ) {
     103                                        try {
     104                                                // from the start date, advance the week until it's past now
     105                                                $start = new DateTime( sprintf( '%s %s GMT', $post->start_date, $post->time ) );
     106                                                $next  = $start;
     107                                                // minus 30 minutes to account for currently ongoing meetings
     108                                                $now = new DateTime( '-30 minutes' );
     109
     110                                                if ( $next < $now ) {
     111                                                        $interval = $start->diff( $now );
     112                                                        // add one to days to account for events that happened earlier today
     113                                                        $weekdiff = ceil( ( $interval->days + 1 ) / 7 );
     114                                                        $next->modify( '+ ' . $weekdiff . ' weeks' );
     115                                                }
     116
     117                                                $post->next_date = $next->format( 'Y-m-d' );
     118                                        } catch ( Exception $e ) {
     119                                                // if the datetime is invalid, then set the post->next_date to the start date instead
     120                                                $post->next_date = $post->start_date;
     121                                        }
     122                                } elseif ( 'biweekly' === $post->recurring ) {
     123                                        try {
     124                                                // advance the start date 2 weeks at a time until it's past now
     125                                                $start = new DateTime( sprintf( '%s %s GMT', $post->start_date, $post->time ) );
     126                                                $next  = $start;
     127                                                // minus 30 minutes to account for currently ongoing meetings
     128                                                $now = new DateTime( '-30 minutes' );
     129
     130                                                while ( $next < $now ) {
     131                                                        $next->modify( '+2 weeks' );
     132                                                }
     133
     134                                                $post->next_date = $next->format( 'Y-m-d' );
     135                                        } catch ( Exception $e ) {
     136                                                // if the datetime is invalid, then set the post->next_date to the start date instead
     137                                                $post->next_date = $post->start_date;
     138                                        }
     139                                } elseif ( 'occurrence' === $post->recurring ) {
     140                                        try {
     141                                                // advance the occurrence day in the current month until it's past now
     142                                                $start = new DateTime( sprintf( '%s %s GMT', $post->start_date, $post->time ) );
     143                                                $next  = $start;
     144                                                // minus 30 minutes to account for currently ongoing meetings
     145                                                $now = new DateTime( '-30 minutes' );
     146
     147                                                $day_index = date( 'w', strtotime( sprintf( '%s %s GMT', $post->start_date, $post->time ) ) );
     148                                                $day_name  = $GLOBALS['wp_locale']->get_weekday( $day_index );
     149                                                $numerals  = array( 'first', 'second', 'third', 'fourth' );
     150                                                $months    = array( 'this month', 'next month' );
     151
     152                                                foreach ( $months as $month ) {
     153                                                        foreach ( $post->occurrence as $index ) {
     154                                                                $next = new DateTime( sprintf( '%s %s of %s %s GMT', $numerals[ $index - 1 ], $day_name, $month, $post->time ) );
     155                                                                if ( $next > $now ) {
     156                                                                        break 2;
     157                                                                }
     158                                                        }
     159                                                }
     160
     161                                                $post->next_date = $next->format( 'Y-m-d' );
     162                                        } catch ( Exception $e ) {
     163                                                // if the datetime is invalid, then set the post->next_date to the start date instead
     164                                                $post->next_date = $post->start_date;
     165                                        }
     166                                } elseif ( 'monthly' === $post->recurring ) {
     167                                        try {
     168                                                // advance the start date 1 month at a time until it's past now
     169                                                $start = new DateTime( sprintf( '%s %s GMT', $post->start_date, $post->time ) );
     170                                                $next  = $start;
     171                                                // minus 30 minutes to account for currently ongoing meetings
     172                                                $now = new DateTime( '-30 minutes' );
     173
     174                                                while ( $next < $now ) {
     175                                                        $next->modify( '+1 month' );
     176                                                }
     177
     178                                                $post->next_date = $next->format( 'Y-m-d' );
     179                                        } catch ( Exception $e ) {
     180                                                // if the datetime is invalid, then set the post->next_date to the start date instead
     181                                                $post->next_date = $post->start_date;
     182                                        }
     183                                } else {
     184                                        $post->next_date = $post->start_date;
     185                                }
     186                        }
     187                );
     188
     189                // reorder the posts by next_date + time
     190                usort(
     191                        $posts,
     192                        function ( $a, $b ) {
     193                                $adate = strtotime( $a->next_date . ' ' . $a->time );
     194                                $bdate = strtotime( $b->next_date . ' ' . $b->time );
     195                                if ( $adate == $bdate ) {
     196                                        return 0;
     197                                }
     198                                return ( $adate < $bdate ) ? -1 : 1;
     199                        }
     200                );
     201
     202                return $posts;
     203        }
     204
     205        public function register_meeting_post_type() {
     206                $labels = array(
     207                        'name'               => _x( 'Meetings', 'Post Type General Name', 'wporg' ),
     208                        'singular_name'      => _x( 'Meeting', 'Post Type Singular Name', 'wporg' ),
     209                        'menu_name'          => __( 'Meetings', 'wporg' ),
     210                        'name_admin_bar'     => __( 'Meeting', 'wporg' ),
     211                        'parent_item_colon'  => __( 'Parent Meeting:', 'wporg' ),
     212                        'all_items'          => __( 'All Meetings', 'wporg' ),
     213                        'add_new_item'       => __( 'Add New Meeting', 'wporg' ),
     214                        'add_new'            => __( 'Add New', 'wporg' ),
     215                        'new_item'           => __( 'New Meeting', 'wporg' ),
     216                        'edit_item'          => __( 'Edit Meeting', 'wporg' ),
     217                        'update_item'        => __( 'Update Meeting', 'wporg' ),
     218                        'view_item'          => __( 'View Meeting', 'wporg' ),
     219                        'view_items'         => __( 'View Meetings', 'wporg' ),
     220                        'search_items'       => __( 'Search Meeting', 'wporg' ),
     221                        'not_found'          => __( 'Not found', 'wporg' ),
     222                        'not_found_in_trash' => __( 'Not found in Trash', 'wporg' ),
     223                );
     224                $args   = array(
     225                        'label'                => __( 'meeting', 'wporg' ),
     226                        'description'          => __( 'Meeting', 'wporg' ),
     227                        'labels'               => $labels,
     228                        'supports'             => array( 'title' ),
     229                        'hierarchical'         => false,
     230                        'public'               => true,
     231                        'show_ui'              => true,
     232                        'show_in_menu'         => true,
     233                        'menu_position'        => 20,
     234                        'menu_icon'            => 'dashicons-calendar',
     235                        'show_in_admin_bar'    => true,
     236                        'show_in_nav_menus'    => false,
     237                        'can_export'           => false,
     238                        'has_archive'          => true,
     239                        'exclude_from_search'  => true,
     240                        'publicly_queryable'   => true,
     241                        'capability_type'      => 'post',
     242                        'register_meta_box_cb' => array( $this, 'add_meta_boxes' ),
     243                        'rewrite'              => array(
     244                                'with_front' => false,
     245                                'slug'       => __( 'meetings', 'wporg' ),
     246                        ),
     247                );
     248                register_post_type( 'meeting', $args );
     249        }
     250
     251        public function add_meta_boxes() {
     252                add_meta_box(
     253                        'meeting-info',
     254                        'Meeting Info',
     255                        array( $this, 'render_meta_boxes' ),
     256                        'meeting',
     257                        'normal',
     258                        'high'
     259                );
     260        }
     261
     262        function render_meta_boxes( $post ) {
     263                wp_enqueue_script( 'jquery-ui-datepicker' );
     264                wp_enqueue_style( 'jquery-ui-style', 'https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/themes/smoothness/jquery-ui.css', true );
     265
     266                $meta      = get_post_custom( $post->ID );
     267                $team      = isset( $meta['team'][0] ) ? $meta['team'][0] : '';
     268                $start     = isset( $meta['start_date'][0] ) ? $meta['start_date'][0] : '';
     269                $end       = isset( $meta['end_date'][0] ) ? $meta['end_date'][0] : '';
     270                $time      = isset( $meta['time'][0] ) ? $meta['time'][0] : '';
     271                $recurring = isset( $meta['recurring'][0] ) ? $meta['recurring'][0] : '';
     272                if ( '1' === $recurring ) {
     273                        $recurring = 'weekly';
     274                }
     275                $occurrence = isset( $meta['occurrence'][0] ) ? unserialize( $meta['occurrence'][0] ) : array();
     276                $link       = isset( $meta['link'][0] ) ? $meta['link'][0] : '';
     277                $location   = isset( $meta['location'][0] ) ? $meta['location'][0] : '';
     278                wp_nonce_field( 'save_meeting_meta_' . $post->ID, 'meeting_nonce' );
     279                ?>
     280
     281                <p>
     282                        <label for="team">
     283                                <?php _e( 'Team: ', 'wporg' ); ?>
     284                                <input type="text" id="team" name="team" class="regular-text wide" value="<?php echo esc_attr( $team ); ?>">
     285                        </label>
     286                </p>
     287                <p>
     288                        <label for="start_date">
     289                                <?php _e( 'Start Date', 'wporg' ); ?>
     290                                <input type="text" name="start_date" id="start_date" class="date" value="<?php echo esc_attr( $start ); ?>">
     291                        </label>
     292                        <label for="end_date">
     293                                <?php _e( 'End Date', 'wporg' ); ?>
     294                                <input type="text" name="end_date" id="end_date" class="date" value="<?php echo esc_attr( $end ); ?>">
     295                        </label>
     296                </p>
     297                <p>
     298                        <label for="time">
     299                                <?php _e( 'Time (UTC)', 'wporg' ); ?>
     300                                <input type="text" name="time" id="time" class="time" value="<?php echo esc_attr( $time ); ?>">
     301                        </label>
     302                </p>
     303                <p class="recurring">
     304                        <?php _e( 'Recurring: ', 'wporg' ); ?><br />
     305                        <label for="weekly">
     306                                <input type="radio" name="recurring" value="weekly" id="weekly" class="regular-radio" <?php checked( $recurring, 'weekly' ); ?>>
     307                                <?php _e( 'Weekly', 'wporg' ); ?>
     308                        </label><br />
     309
     310                        <label for="biweekly">
     311                                <input type="radio" name="recurring" value="biweekly" id="biweekly" class="regular-radio" <?php checked( $recurring, 'biweekly' ); ?>>
     312                                <?php _e( 'Biweekly', 'wporg' ); ?>
     313                        </label><br />
     314
     315                        <label for="occurrence">
     316                                <input type="radio" name="recurring" value="occurrence" id="occurrence" class="regular-radio" <?php checked( $recurring, 'occurrence' ); ?>>
     317                                <?php _e( 'Occurrence in a month:', 'wporg' ); ?>
     318                        </label>
     319                        <label for="week-1">
     320                                <input type="checkbox" name="occurrence[]" value="1" id="week-1" <?php checked( in_array( 1, $occurrence ) ); ?>>
     321                                <?php _e( '1st', 'wporg' ); ?>
     322                        </label>
     323                        <label for="week-2">
     324                                <input type="checkbox" name="occurrence[]" value="2" id="week-2" <?php checked( in_array( 2, $occurrence ) ); ?>>
     325                                <?php _e( '2nd', 'wporg' ); ?>
     326                        </label>
     327                        <label for="week-3">
     328                                <input type="checkbox" name="occurrence[]" value="3" id="week-3" <?php checked( in_array( 3, $occurrence ) ); ?>>
     329                                <?php _e( '3rd', 'wporg' ); ?>
     330                        </label>
     331                        <label for="week-4">
     332                                <input type="checkbox" name="occurrence[]" value="4" id="week-4" <?php checked( in_array( 4, $occurrence ) ); ?>>
     333                                <?php _e( '4th', 'wporg' ); ?>
     334                        </label><br />
     335
     336                        <label for="monthly">
     337                                <input type="radio" name="recurring" value="monthly" id="monthly" class="regular-radio" <?php checked( $recurring, 'monthly' ); ?>>
     338                                <?php _e( 'Monthly', 'wporg' ); ?>
     339                        </label>
     340                </p>
     341                <p>
     342                        <label for="link"><?php _e( 'Link: ', 'wporg' ); ?>
     343                                <input type="text" name="link" id="link" class="regular-text wide" value="<?php echo esc_url( $link ); ?>">
     344                        </label>
     345                </p>
     346                <p>
     347                        <label for="location"><?php _e( 'Location: ', 'wporg' ); ?>
     348                                <input type="text" name="location" id="location" class="regular-text wide" value="<?php echo esc_attr( $location ); ?>">
     349                        </label>
     350                </p>
     351                <script>
     352                        jQuery(document).ready( function($) {
     353                                $('.date').datepicker({
     354                                        dateFormat: 'yy-mm-dd'
     355                                });
     356
     357                                $('input[name="recurring"]').change( function() {
     358                                        var disabled = ( 'occurrence' !== $(this).val() );
     359                                        $('#meeting-info').find('[name^="occurrence"]').prop('disabled', disabled);
     360                                });
     361
     362                                if ( 'occurrence' !== $('input[name="recurring"]:checked').val() ) {
     363                                        $('#meeting-info').find('[name^="occurrence"]').prop('disabled', true);
     364                                }
     365                        });
     366                </script>
     367                <?php
     368        }
     369
     370        function save_meta_boxes( $post_id ) {
     371
     372                global $post;
     373
     374                // Verify nonce
     375                if ( ! isset( $_POST['meeting_nonce'] ) || ! wp_verify_nonce( $_POST['meeting_nonce'], 'save_meeting_meta_' . $post_id ) ) {
     376                        return $post_id;
     377                }
     378
     379                // Check autosave
     380                if ( ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) || ( defined( 'DOING_AJAX' ) && DOING_AJAX ) || isset( $_REQUEST['bulk_edit'] ) ) {
     381                        return $post_id;
     382                }
     383
     384                // Don't save for revisions
     385                if ( isset( $post->post_type ) && 'revision' === $post->post_type ) {
     386                        return $post_id;
     387                }
     388
     389                // Check permissions
     390                if ( ! current_user_can( 'edit_post', $post->ID ) ) {
     391                        return $post_id;
     392                }
     393
     394                $meta['team']       = ( isset( $_POST['team'] ) ? esc_textarea( $_POST['team'] ) : '' );
     395                $meta['start_date'] = ( isset( $_POST['start_date'] ) ? esc_textarea( $_POST['start_date'] ) : '' );
     396                $meta['end_date']   = ( isset( $_POST['end_date'] ) ? esc_textarea( $_POST['end_date'] ) : '' );
     397                $meta['time']       = ( isset( $_POST['time'] ) ? esc_textarea( $_POST['time'] ) : '' );
     398                $meta['recurring']  = ( isset( $_POST['recurring'] )
     399                && in_array( $_POST['recurring'], array( 'weekly', 'biweekly', 'occurrence', 'monthly' ) )
     400                        ? ( $_POST['recurring'] ) : '' );
     401                $meta['occurrence'] = ( isset( $_POST['occurrence'] ) && 'occurrence' === $meta['recurring']
     402                && is_array( $_POST['occurrence'] )
     403                        ? array_map( 'intval', $_POST['occurrence'] ) : array() );
     404                $meta['link']       = ( isset( $_POST['link'] ) ? esc_url( $_POST['link'] ) : '' );
     405                $meta['location']   = ( isset( $_POST['location'] ) ? esc_textarea( $_POST['location'] ) : '' );
     406
     407                foreach ( $meta as $key => $value ) {
     408                        update_post_meta( $post->ID, $key, $value );
     409                }
     410        }
     411
     412        /**
     413         * Adds "Edit Meetings" item after "Add New" menu.
     414         *
     415         * @param \WP_Admin_Bar $wp_admin_bar The admin bar instance.
     416         */
     417        public function add_edit_meetings_item_to_admin_bar( $wp_admin_bar ) {
     418                if ( ! current_user_can( 'edit_posts' ) ) {
     419                        return;
     420                }
     421
     422                if ( is_admin() || ! is_post_type_archive( 'meeting' ) ) {
     423                        return;
     424                }
     425
     426                $wp_admin_bar->add_menu(
     427                        array(
     428                                'id'    => 'edit-meetings',
     429                                'title' => '<span class="ab-icon"></span>' . __( 'Edit Meetings', 'wporg' ),
     430                                'href'  => admin_url( 'edit.php?post_type=meeting' ),
     431                        )
     432                );
     433        }
     434
     435        /**
     436         * Adds icon for the "Edit Meetings" item.
     437         */
     438        public function add_edit_meetings_icon_to_admin_bar() {
     439                if ( ! current_user_can( 'edit_posts' ) ) {
     440                        return;
     441                }
     442
     443                wp_add_inline_style(
     444                        'admin-bar',
     445                        '
     446                        #wpadminbar #wp-admin-bar-edit-meetings .ab-icon:before {
     447                                content: "\f145";
     448                                top: 2px;
     449                        }
     450                '
     451                );
     452        }
     453
     454        /**
     455         * Renders meeting information with the next meeting time based on user's local timezone. Used in Make homepage.
     456         */
     457        public function meeting_time_shortcode( $attr, $content = '' ) {
     458
     459                $attr = shortcode_atts(
     460                        array(
     461                                'team'     => null,
     462                                'limit'    => 1,
     463                                'before'   => __( 'Next meeting: ', 'wporg' ),
     464                                'titletag' => 'strong',
     465                                'more'     => true,
     466                        ),
     467                        $attr
     468                );
     469
     470                if ( empty( $attr['team'] ) ) {
     471                        return '';
     472                }
     473
     474                if ( $attr['team'] === 'Documentation' ) {
     475                        $attr['team'] = 'Docs';
     476                }
     477
     478                if ( ! has_action( 'wp_footer', array( $this, 'time_conversion_script' ) ) ) {
     479                        add_action( 'wp_footer', array( $this, 'time_conversion_script' ), 999 );
     480                }
     481
     482                $query = new Meeting_Query( $attr['team'] );
     483                $posts = $query->get_posts();
     484
     485                $limit = $attr['limit'] > 0 ? $attr['limit'] : count( $posts );
     486
     487                $out = '';
     488                foreach ( array_slice( $posts, 0, $limit ) as $post ) {
     489                        $next_meeting_datestring = $post->next_date;
     490                        $utc_time                = strftime( '%H:%M:%S', strtotime( $post->time ) );
     491                        $next_meeting_iso        = $next_meeting_datestring . 'T' . $utc_time . '+00:00';
     492                        $next_meeting_timestamp  = strtotime( $next_meeting_datestring . ' ' . $utc_time );
     493                        $next_meeting_display    = strftime( '%c %Z', $next_meeting_timestamp );
     494
     495                        $slack_channel = null;
     496                        if ( $post->location && preg_match( '/^#([-\w]+)$/', trim( $post->location ), $match ) ) {
     497                                $slack_channel = sanitize_title( $match[1] );
     498                        }
     499
     500                        $out         .= '<p>';
     501                        $out         .= esc_html( $attr['before'] );
     502                        $out         .= '<strong class="meeting-title">' . esc_html( $post->post_title ) . '</strong>';
     503                        $display_more = $query->found_posts - intval( $limit );
     504                        if ( $display_more > 0 ) {
     505                                $out .= ' <a title="Click to view all meetings for this team" href="/meetings/#' . esc_attr( strtolower( $attr['team'] ) ) . '">' . sprintf( __( '(+%s more)' ), $display_more ) . '</a>';
     506                        }
     507                        $out .= '</br>';
     508                        $out .= '<time class="date" date-time="' . esc_attr( $next_meeting_iso ) . '" title="' . esc_attr( $next_meeting_iso ) . '">' . $next_meeting_display . '</time> ';
     509                        $out .= sprintf( esc_html__( '(%s from now)' ), human_time_diff( $next_meeting_timestamp, current_time( 'timestamp' ) ) );
     510                        if ( $post->location && $slack_channel ) {
     511                                $out .= ' ' . sprintf( wp_kses( __( 'at <a href="%1$s">%2$s</a> on Slack' ), array( 'a' => array( 'href' => array() ) ) ), 'https://wordpress.slack.com/messages/' . $slack_channel, $post->location );
     512                        }
     513                        $out .= '</p>';
     514                }
     515
     516                return $out;
     517        }
     518
     519        public function time_conversion_script() {
     520                echo <<<EOF
     521<script type="text/javascript">
     522
     523        var parse_date = function (text) {
     524                var m = /^([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})\+00:00$/.exec(text);
     525                var d = new Date();
     526                d.setUTCFullYear(+m[1]);
     527                d.setUTCDate(+m[3]);
     528                d.setUTCMonth(+m[2]-1);
     529                d.setUTCHours(+m[4]);
     530                d.setUTCMinutes(+m[5]);
     531                d.setUTCSeconds(+m[6]);
     532                return d;
     533        }
     534        var format_time = function (d) {
     535                return d.toLocaleTimeString(navigator.language, {weekday: 'long', hour: '2-digit', minute: '2-digit', timeZoneName: 'short'});
     536        }
     537
     538        var nodes = document.getElementsByTagName('time');
     539        for (var i=0; i<nodes.length; ++i) {
     540                var node = nodes[i];
     541                if (node.className === 'date') {
     542                        var d = parse_date(node.getAttribute('date-time'));
     543                        if (d) {
     544                                node.textContent = format_time(d);
     545                        }
     546                }
     547        }
     548</script>
     549EOF;
     550        }
     551}
  • new file wordpress.org/public_html/wp-content/plugins/wporg-meeting-posttype/inc/common/class-meeting-query.php

    diff --git wordpress.org/public_html/wp-content/plugins/wporg-meeting-posttype/inc/common/class-meeting-query.php wordpress.org/public_html/wp-content/plugins/wporg-meeting-posttype/inc/common/class-meeting-query.php
    new file mode 100644
    index 000000000..76fdf35eb
    - +  
     1<?php
     2
     3namespace WordPressdotorg\Meetings\Common;
     4
     5use WP_Query;
     6
     7class Meeting_Query {
     8
     9        /**
     10         * @var WP_Query
     11         */
     12        private $query;
     13
     14        public function __construct( $team ) {
     15                $this->query = new WP_Query();
     16                $this->build_query( $team );
     17        }
     18
     19        public static function get_meta_query() {
     20                return array(
     21                        'relation' => 'OR',
     22                        // not recurring  AND start_date >= CURDATE() = one-time meeting today or still in future
     23                        array(
     24                                'relation' => 'AND',
     25                                array(
     26                                        'key'     => 'recurring',
     27                                        'value'   => array( 'weekly', 'biweekly', 'occurrence', 'monthly', '1' ),
     28                                        'compare' => 'NOT IN',
     29                                ),
     30                                array(
     31                                        'key'     => 'start_date',
     32                                        'type'    => 'DATE',
     33                                        'compare' => '>=',
     34                                        'value'   => 'CURDATE()',
     35                                ),
     36                        ),
     37                        // recurring = 1 AND ( end_date = '' OR end_date > CURDATE() ) = recurring meeting that has no end or has not ended yet
     38                        array(
     39                                'relation' => 'AND',
     40                                array(
     41                                        'key'     => 'recurring',
     42                                        'value'   => array( 'weekly', 'biweekly', 'occurrence', 'monthly', '1' ),
     43                                        'compare' => 'IN',
     44                                ),
     45                                array(
     46                                        'relation' => 'OR',
     47                                        array(
     48                                                'key'     => 'end_date',
     49                                                'value'   => '',
     50                                                'compare' => '=',
     51                                        ),
     52                                        array(
     53                                                'key'     => 'end_date',
     54                                                'type'    => 'DATE',
     55                                                'compare' => '>',
     56                                                'value'   => 'CURDATE()',
     57                                        ),
     58                                ),
     59                        ),
     60                );
     61        }
     62
     63        public function get_posts() {
     64                return $this->query->get_posts();
     65        }
     66
     67        private function build_query( $team ) {
     68                // meta query to eliminate expired meetings from query
     69                add_filter(
     70                        'get_meta_sql',
     71                        function ( $sql ) {
     72                                return str_replace( "'CURDATE()'", 'CURDATE()', $sql );
     73                        }
     74                );
     75
     76                switch_to_blog( get_main_site_id() );
     77
     78                $this->query->set( 'post_type', 'meeting' );
     79                $this->query->set( 'nopaging', true );
     80                $this->query->set(
     81                        'meta_query',
     82                        array(
     83                                'relation' => 'AND',
     84                                array(
     85                                        'key'     => 'team',
     86                                        'value'   => $team,
     87                                        'compare' => empty( $team ) ? '!=' : '=',
     88                                ),
     89                                self::get_meta_query(),
     90                        )
     91                );
     92
     93                restore_current_blog();
     94        }
     95}
  • new file wordpress.org/public_html/wp-content/plugins/wporg-meeting-posttype/vendor/wordpressdotorg/autoload/class-autoloader.php

    diff --git wordpress.org/public_html/wp-content/plugins/wporg-meeting-posttype/vendor/wordpressdotorg/autoload/class-autoloader.php wordpress.org/public_html/wp-content/plugins/wporg-meeting-posttype/vendor/wordpressdotorg/autoload/class-autoloader.php
    new file mode 100644
    index 000000000..8c2258ba3
    - +  
     1<?php
     2namespace WordPressdotorg\Autoload;
     3
     4/**
     5 * An Autoloader which respects WordPress's filename standards.
     6 *
     7 * @package WordPressdotorg\Autoload
     8 */
     9class Autoloader {
     10
     11        /**
     12         * Namespace separator.
     13         */
     14        const NS_SEPARATOR = '\\';
     15
     16        /**
     17         * The prefix to compare classes against.
     18         *
     19         * @var string
     20         * @access protected
     21         */
     22        protected $prefix;
     23
     24        /**
     25         * Length of the prefix string.
     26         *
     27         * @var int
     28         * @access protected
     29         */
     30        protected $prefix_length;
     31
     32        /**
     33         * Path to the file to be loaded.
     34         *
     35         * @var string
     36         * @access protected
     37         */
     38        protected $path;
     39
     40        /**
     41         * Constructor.
     42         *
     43         * @param string $prefix Prefix all classes have in common.
     44         * @param string $path   Path to the files to be loaded.
     45         */
     46        public function __construct( $prefix, $path ) {
     47                $this->prefix        = $prefix;
     48                $this->prefix_length = strlen( $prefix );
     49                $this->path          = trailingslashit( $path );
     50        }
     51
     52        /**
     53         * Loads a class if it starts with `$this->prefix`.
     54         *
     55         * @param string $class The class to be loaded.
     56         */
     57        public function load( $class ) {
     58                if ( strpos( $class, $this->prefix . self::NS_SEPARATOR ) !== 0 ) {
     59                        return;
     60                }
     61
     62                // Strip prefix from the start (ala PSR-4)
     63                $class = substr( $class, $this->prefix_length + 1 );
     64                $class = strtolower( $class );
     65                $file  = '';
     66
     67                if ( false !== ( $last_ns_pos = strripos( $class, self::NS_SEPARATOR ) ) ) {
     68                        $namespace = substr( $class, 0, $last_ns_pos );
     69                        $namespace = str_replace( '_', '-', $namespace );
     70                        $class     = substr( $class, $last_ns_pos + 1 );
     71                        $file      = str_replace( self::NS_SEPARATOR, DIRECTORY_SEPARATOR, $namespace ) . DIRECTORY_SEPARATOR;
     72                }
     73
     74                $file .= 'class-' . str_replace( '_', '-', $class ) . '.php';
     75
     76                $path = $this->path . $file;
     77
     78                if ( file_exists( $path ) ) {
     79                        require $path;
     80                }
     81        }
     82}
     83
     84/**
     85 * Registers Autoloader's autoload function.
     86 *
     87 * @param string $prefix
     88 * @param string $path
     89 */
     90function register_class_path( $prefix, $path ) {
     91        $loader = new Autoloader( $prefix, $path );
     92        spl_autoload_register( array( $loader, 'load' ) );
     93}
  • wordpress.org/public_html/wp-content/plugins/wporg-meeting-posttype/wporg-meeting-posttype.php

    diff --git wordpress.org/public_html/wp-content/plugins/wporg-meeting-posttype/wporg-meeting-posttype.php wordpress.org/public_html/wp-content/plugins/wporg-meeting-posttype/wporg-meeting-posttype.php
    index 9dd64b010..0b58b2ce5 100644
     
    22/*
    33Plugin Name: WPORG Make Homepage Meeting Post Type
    44Description: Creates the meeting post type and assorted filters for https://make.wordpress.org/meetings
    5 Version:     1.0
     5Version:     1.0.1
    66License:     GPLv2 or later
    77Author:      WordPress.org
    88Author URI:  http://wordpress.org/
    99Text Domain: wporg
    1010*/
    1111
    12 if ( !class_exists('Meeting_Post_Type') ):
    13 class Meeting_Post_Type {
     12namespace WordPressdotorg\Meetings\PostType;
    1413
    15         protected static $instance = NULL;
     14use WordPressdotorg\Autoload;
    1615
    17         public static function getInstance() {
    18                 NULL === self::$instance and self::$instance = new self;
    19                 return self::$instance;
    20         }
    21 
    22         public static function init() {
    23                 $mpt = Meeting_Post_Type::getInstance();
    24                 add_action( 'init',                               array( $mpt, 'register_meeting_post_type' ) );
    25                 add_action( 'save_post_meeting',                  array( $mpt, 'save_meta_boxes' ), 10, 2 );
    26                 add_filter( 'pre_get_posts',                      array( $mpt, 'meeting_archive_page_query' ) );
    27                 add_filter( 'the_posts',                          array( $mpt, 'meeting_set_next_meeting' ), 10, 2 );
    28                 add_filter( 'manage_meeting_posts_columns',       array( $mpt, 'meeting_add_custom_columns' ) );
    29                 add_action( 'manage_meeting_posts_custom_column', array( $mpt, 'meeting_custom_columns' ), 10, 2 );
    30                 add_action( 'admin_head',                         array( $mpt, 'meeting_column_width' ) );
    31                 add_action( 'admin_bar_menu',                     array( $mpt, 'add_edit_meetings_item_to_admin_bar' ), 80 );
    32                 add_action( 'wp_enqueue_scripts',                 array( $mpt, 'add_edit_meetings_icon_to_admin_bar' ) );
    33                 add_shortcode( 'meeting_time',                    array( $mpt, 'meeting_time_shortcode' ) );
    34         }
    35 
    36         public function meeting_column_width() { ?>
    37                 <style type="text/css">
    38                         .column-team { width: 10em !important; overflow: hidden; }
    39                         #meeting-info .recurring label { padding-right: 10px; }
    40                 </style>
    41                 <?php
    42         }
    43 
    44         public function meeting_add_custom_columns( $columns ) {
    45                 $columns = array_slice( $columns, 0, 1, true )
    46                         + array( 'team' => __('Team', 'wporg') )
    47                         + array_slice( $columns, 1, null, true );
    48                 return $columns;
    49         }
    50 
    51         public function meeting_custom_columns( $column, $post_id ) {
    52                 switch ( $column ) {
    53                 case 'team' :
    54                         $team = get_post_meta( $post_id, 'team', true );
    55                         echo esc_html( $team );
    56                         break;
    57                 }
    58         }
    59 
    60         public function meeting_archive_page_query( $query ) {
    61                 if ( is_admin() || ! $query->is_main_query() || ! $query->is_post_type_archive( 'meeting' ) ) {
    62                         return;
    63                 }
    64                 // turn off paging on the archive page, to show all meetings in the table
    65                 $query->set( 'nopaging', true );
    66 
    67                 // meta query to eliminate expired meetings from query
    68                 $query->set( 'meta_query', $this->meeting_meta_query );
    69 
    70                 // WP doesn't understand CURDATE() and prepares it as a quoted string. Repair this:
    71                 add_filter( 'get_meta_sql', function ($sql) {
    72                         return str_replace( "'CURDATE()'", 'CURDATE()', $sql );
    73                 } );
    74 
    75         }
    76 
    77         public function meeting_set_next_meeting( $posts, $query ) {
    78                 if ( !$query->is_post_type_archive( 'meeting' ) ) {
    79                         return $posts;
    80                 }
    81 
    82                 // for each entry, set a fake meta value to show the next date for recurring meetings
    83                 array_walk( $posts, function ( &$post ) {
    84                         if ( 'weekly' === $post->recurring || '1' === $post->recurring ) {
    85                                 try {
    86                                         // from the start date, advance the week until it's past now
    87                                         $start = new DateTime( sprintf( '%s %s GMT', $post->start_date, $post->time ) );
    88                                         $next  = $start;
    89                                         // minus 30 minutes to account for currently ongoing meetings
    90                                         $now   = new DateTime( '-30 minutes' );
    91 
    92                                         if ( $next < $now ) {
    93                                                 $interval = $start->diff( $now );
    94                                                 // add one to days to account for events that happened earlier today
    95                                                 $weekdiff = ceil( ( $interval->days + 1 ) / 7 );
    96                                                 $next->modify( '+ ' . $weekdiff . ' weeks' );
    97                                         }
    98 
    99                                         $post->next_date = $next->format( 'Y-m-d' );
    100                                 } catch ( Exception $e ) {
    101                                         // if the datetime is invalid, then set the post->next_date to the start date instead
    102                                         $post->next_date = $post->start_date;
    103                                 }
    104                         } else if ( 'biweekly' === $post->recurring ) {
    105                                 try {
    106                                         // advance the start date 2 weeks at a time until it's past now
    107                                         $start = new DateTime( sprintf( '%s %s GMT', $post->start_date, $post->time ) );
    108                                         $next  = $start;
    109                                         // minus 30 minutes to account for currently ongoing meetings
    110                                         $now   = new DateTime( '-30 minutes' );
    111 
    112                                         while ( $next < $now ) {
    113                                                 $next->modify( '+2 weeks' );
    114                                         }
    115 
    116                                         $post->next_date = $next->format( 'Y-m-d' );
    117                                 } catch ( Exception $e ) {
    118                                         // if the datetime is invalid, then set the post->next_date to the start date instead
    119                                         $post->next_date = $post->start_date;
    120                                 }
    121                         } else if ( 'occurrence' === $post->recurring ) {
    122                                 try {
    123                                         // advance the occurrence day in the current month until it's past now
    124                                         $start = new DateTime( sprintf( '%s %s GMT', $post->start_date, $post->time ) );
    125                                         $next  = $start;
    126                                         // minus 30 minutes to account for currently ongoing meetings
    127                                         $now   = new DateTime( '-30 minutes' );
    128 
    129                                         $day_index = date( 'w', strtotime( sprintf( '%s %s GMT', $post->start_date, $post->time ) ) );
    130                                         $day_name  = $GLOBALS['wp_locale']->get_weekday( $day_index );
    131                                         $numerals  = array( 'first', 'second', 'third', 'fourth' );
    132                                         $months    = array( 'this month', 'next month' );
    133 
    134                                         foreach ( $months as $month ) {
    135                                                 foreach ( $post->occurrence as $index ) {
    136                                                         $next = new DateTime( sprintf( '%s %s of %s %s GMT', $numerals[ $index - 1 ], $day_name, $month, $post->time ) );
    137                                                         if ( $next > $now ) {
    138                                                                 break 2;
    139                                                         }
    140                                                 }
    141                                         }
    142 
    143                                         $post->next_date = $next->format( 'Y-m-d' );
    144                                 } catch ( Exception $e ) {
    145                                         // if the datetime is invalid, then set the post->next_date to the start date instead
    146                                         $post->next_date = $post->start_date;
    147                                 }
    148                         } else if ( 'monthly' === $post->recurring ) {
    149                                 try {
    150                                         // advance the start date 1 month at a time until it's past now
    151                                         $start = new DateTime( sprintf( '%s %s GMT', $post->start_date, $post->time ) );
    152                                         $next  = $start;
    153                                         // minus 30 minutes to account for currently ongoing meetings
    154                                         $now   = new DateTime( '-30 minutes' );
    155 
    156                                         while ( $next < $now ) {
    157                                                 $next->modify( '+1 month' );
    158                                         }
    159 
    160                                         $post->next_date = $next->format( 'Y-m-d' );
    161                                 } catch ( Exception $e ) {
    162                                         // if the datetime is invalid, then set the post->next_date to the start date instead
    163                                         $post->next_date = $post->start_date;
    164                                 }
    165                         } else {
    166                                 $post->next_date = $post->start_date;
    167                         }
    168                 });
    169 
    170                 // reorder the posts by next_date + time
    171                 usort( $posts, function ($a, $b) {
    172                         $adate = strtotime( $a->next_date . ' ' . $a->time );
    173                         $bdate = strtotime( $b->next_date . ' ' . $b->time );
    174                         if ( $adate == $bdate ) {
    175                                 return 0;
    176                         }
    177                         return ( $adate < $bdate ) ? -1 : 1;
    178                 });
    179 
    180                 return $posts;
    181         }
    182 
    183         public function register_meeting_post_type() {
    184             $labels = array(
    185                 'name'                => _x( 'Meetings', 'Post Type General Name', 'wporg' ),
    186                 'singular_name'       => _x( 'Meeting', 'Post Type Singular Name', 'wporg' ),
    187                 'menu_name'           => __( 'Meetings', 'wporg' ),
    188                 'name_admin_bar'      => __( 'Meeting', 'wporg' ),
    189                 'parent_item_colon'   => __( 'Parent Meeting:', 'wporg' ),
    190                 'all_items'           => __( 'All Meetings', 'wporg' ),
    191                 'add_new_item'        => __( 'Add New Meeting', 'wporg' ),
    192                 'add_new'             => __( 'Add New', 'wporg' ),
    193                 'new_item'            => __( 'New Meeting', 'wporg' ),
    194                 'edit_item'           => __( 'Edit Meeting', 'wporg' ),
    195                 'update_item'         => __( 'Update Meeting', 'wporg' ),
    196                 'view_item'           => __( 'View Meeting', 'wporg' ),
    197                 'view_items'          => __( 'View Meetings', 'wporg' ),
    198                 'search_items'        => __( 'Search Meeting', 'wporg' ),
    199                 'not_found'           => __( 'Not found', 'wporg' ),
    200                 'not_found_in_trash'  => __( 'Not found in Trash', 'wporg' ),
    201             );
    202             $args = array(
    203                 'label'               => __( 'meeting', 'wporg' ),
    204                 'description'         => __( 'Meeting', 'wporg' ),
    205                 'labels'              => $labels,
    206                 'supports'            => array( 'title' ),
    207                 'hierarchical'        => false,
    208                 'public'              => true,
    209                 'show_ui'             => true,
    210                 'show_in_menu'        => true,
    211                 'menu_position'       => 20,
    212                 'menu_icon'           => 'dashicons-calendar',
    213                 'show_in_admin_bar'   => true,
    214                 'show_in_nav_menus'   => false,
    215                 'can_export'          => false,
    216                 'has_archive'         => true,
    217                 'exclude_from_search' => true,
    218                 'publicly_queryable'  => true,
    219                 'capability_type'     => 'post',
    220                         'register_meta_box_cb'=> array( $this, 'add_meta_boxes' ),
    221                         'rewrite'             => array(
    222                                 'with_front'      => false,
    223                                 'slug'            => __( 'meetings', 'wporg' ),
    224                         ),
    225             );
    226                 register_post_type( 'meeting', $args );
    227         }
    228 
    229         public function add_meta_boxes() {
    230                 add_meta_box(
    231                         'meeting-info',
    232                         'Meeting Info',
    233                         array( $this, 'render_meta_boxes' ),
    234                         'meeting',
    235                         'normal',
    236                         'high'
    237                 );
    238         }
    239 
    240         function render_meta_boxes( $post ) {
    241                 wp_enqueue_script( 'jquery-ui-datepicker' );
    242                 wp_enqueue_style( 'jquery-ui-style', 'https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/themes/smoothness/jquery-ui.css', true);
    243 
    244                 $meta       = get_post_custom( $post->ID );
    245                 $team       = isset( $meta['team'][0] ) ? $meta['team'][0] : '';
    246                 $start      = isset( $meta['start_date'][0] ) ? $meta['start_date'][0] : '';
    247                 $end        = isset( $meta['end_date'][0] ) ? $meta['end_date'][0] : '';
    248                 $time       = isset( $meta['time'][0] ) ? $meta['time'][0] : '';
    249                 $recurring  = isset( $meta['recurring'][0] ) ? $meta['recurring'][0] : '';
    250                 if ( '1' === $recurring ) {
    251                         $recurring = 'weekly';
    252                 }
    253                 $occurrence = isset( $meta['occurrence'][0] ) ? unserialize( $meta['occurrence'][0] ) : array();
    254                 $link       = isset( $meta['link'][0] ) ? $meta['link'][0] : '';
    255                 $location   = isset( $meta['location'][0] ) ? $meta['location'][0] : '';
    256                 wp_nonce_field( 'save_meeting_meta_'.$post->ID , 'meeting_nonce' );
    257                 ?>
    258 
    259                 <p>
    260                 <label for="team">
    261                         <?php _e( 'Team: ', 'wporg' ); ?>
    262                         <input type="text" id="team" name="team" class="regular-text wide" value="<?php echo esc_attr( $team ); ?>">
    263                 </label>
    264                 </p>
    265                 <p>
    266                 <label for="start_date">
    267                         <?php _e( 'Start Date', 'wporg' ); ?>
    268                         <input type="text" name="start_date" id="start_date" class="date" value="<?php echo esc_attr( $start ); ?>">
    269                 </label>
    270                 <label for="end_date">
    271                         <?php _e( 'End Date', 'wporg' ); ?>
    272                         <input type="text" name="end_date" id="end_date" class="date" value="<?php echo esc_attr( $end ); ?>">
    273                 </label>
    274                 </p>
    275                 <p>
    276                 <label for="time">
    277                         <?php _e( 'Time (UTC)', 'wporg' ); ?>
    278                         <input type="text" name="time" id="time" class="time" value="<?php echo esc_attr( $time ); ?>">
    279                 </label>
    280                 </p>
    281                 <p class="recurring">
    282                 <?php _e( 'Recurring: ', 'wporg' ); ?><br />
    283                 <label for="weekly">
    284                         <input type="radio" name="recurring" value="weekly" id="weekly" class="regular-radio" <?php checked( $recurring, 'weekly' ); ?>>
    285                         <?php _e( 'Weekly', 'wporg' ); ?>
    286                 </label><br />
    287 
    288                 <label for="biweekly">
    289                         <input type="radio" name="recurring" value="biweekly" id="biweekly" class="regular-radio" <?php checked( $recurring, 'biweekly' ); ?>>
    290                         <?php _e( 'Biweekly', 'wporg' ); ?>
    291                 </label><br />
    292 
    293                 <label for="occurrence">
    294                         <input type="radio" name="recurring" value="occurrence" id="occurrence" class="regular-radio" <?php checked( $recurring, 'occurrence' ); ?>>
    295                         <?php _e( 'Occurrence in a month:', 'wporg' ); ?>
    296                 </label>
    297                 <label for="week-1">
    298                         <input type="checkbox" name="occurrence[]" value="1" id="week-1" <?php checked( in_array( 1, $occurrence ) ); ?>>
    299                         <?php _e( '1st', 'wporg' ); ?>
    300                 </label>
    301                 <label for="week-2">
    302                         <input type="checkbox" name="occurrence[]" value="2" id="week-2" <?php checked( in_array( 2, $occurrence ) ); ?>>
    303                         <?php _e( '2nd', 'wporg' ); ?>
    304                 </label>
    305                 <label for="week-3">
    306                         <input type="checkbox" name="occurrence[]" value="3" id="week-3" <?php checked( in_array( 3, $occurrence ) ); ?>>
    307                         <?php _e( '3rd', 'wporg' ); ?>
    308                 </label>
    309                 <label for="week-4">
    310                         <input type="checkbox" name="occurrence[]" value="4" id="week-4" <?php checked( in_array( 4, $occurrence ) ); ?>>
    311                         <?php _e( '4th', 'wporg' ); ?>
    312                 </label><br />
    313 
    314                 <label for="monthly">
    315                         <input type="radio" name="recurring" value="monthly" id="monthly" class="regular-radio" <?php checked( $recurring, 'monthly' ); ?>>
    316                         <?php _e( 'Monthly', 'wporg' ); ?>
    317                 </label>
    318                 </p>
    319                 <p>
    320                 <label for="link"><?php _e( 'Link: ', 'wporg' ); ?>
    321                         <input type="text" name="link" id="link" class="regular-text wide" value="<?php echo esc_url( $link ); ?>">
    322                 </label>
    323                 </p>
    324                 <p>
    325                 <label for="location"><?php _e( 'Location: ', 'wporg' ); ?>
    326                         <input type="text" name="location" id="location" class="regular-text wide" value="<?php echo esc_attr( $location ); ?>">
    327                 </label>
    328                 </p>
    329                 <script>
    330                 jQuery(document).ready( function($) {
    331                         $('.date').datepicker({
    332                                 dateFormat: 'yy-mm-dd'
    333                         });
    334 
    335                         $('input[name="recurring"]').change( function() {
    336                                 var disabled = ( 'occurrence' !== $(this).val() );
    337                                 $('#meeting-info').find('[name^="occurrence"]').prop('disabled', disabled);
    338                         });
    339 
    340                         if ( 'occurrence' !== $('input[name="recurring"]:checked').val() ) {
    341                                 $('#meeting-info').find('[name^="occurrence"]').prop('disabled', true);
    342                         }
    343                 });
    344                 </script>
    345         <?php
    346         }
    347 
    348         function save_meta_boxes( $post_id ) {
    349 
    350                 global $post;
    351 
    352                 // Verify nonce
    353                 if ( !isset( $_POST['meeting_nonce'] ) || !wp_verify_nonce( $_POST['meeting_nonce'], 'save_meeting_meta_'.$post_id ) ) {
    354                         return $post_id;
    355                 }
    356 
    357                 // Check autosave
    358                 if ( (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) || ( defined('DOING_AJAX') && DOING_AJAX) || isset($_REQUEST['bulk_edit']) ) {
    359                         return $post_id;
    360                 }
    361 
    362                 // Don't save for revisions
    363                 if ( isset( $post->post_type ) && 'revision' === $post->post_type ) {
    364                         return $post_id;
    365                 }
    366 
    367                 // Check permissions
    368                 if ( !current_user_can( 'edit_post', $post->ID ) ) {
    369                         return $post_id;
    370                 }
    371 
    372                 $meta['team']        = ( isset( $_POST['team'] ) ? esc_textarea( $_POST['team'] ) : '' );
    373                 $meta['start_date']  = ( isset( $_POST['start_date'] ) ? esc_textarea( $_POST['start_date'] ) : '' );
    374                 $meta['end_date']    = ( isset( $_POST['end_date'] ) ? esc_textarea( $_POST['end_date'] ) : '' );
    375                 $meta['time']        = ( isset( $_POST['time'] ) ? esc_textarea( $_POST['time'] ) : '' );
    376                 $meta['recurring']   = ( isset( $_POST['recurring'] )
    377                                          && in_array( $_POST['recurring'], array( 'weekly', 'biweekly', 'occurrence', 'monthly' ) )
    378                                          ? ( $_POST['recurring'] ) : '' );
    379                 $meta['occurrence']  = ( isset( $_POST['occurrence'] ) && 'occurrence' === $meta['recurring']
    380                                          && is_array( $_POST['occurrence'] )
    381                                          ? array_map( 'intval', $_POST['occurrence'] ) : array() );
    382                 $meta['link']        = ( isset( $_POST['link'] ) ? esc_url( $_POST['link'] ) : '' );
    383                 $meta['location']    = ( isset( $_POST['location'] ) ? esc_textarea( $_POST['location'] ) : '' );
    384 
    385                 foreach ( $meta as $key => $value ) {
    386                         update_post_meta( $post->ID, $key, $value );
    387                 }
    388         }
    389 
    390         /**
    391          * Adds "Edit Meetings" item after "Add New" menu.
    392          *
    393          * @param \WP_Admin_Bar $wp_admin_bar The admin bar instance.
    394          */
    395         public function add_edit_meetings_item_to_admin_bar( $wp_admin_bar ) {
    396                 if ( ! current_user_can( 'edit_posts' ) ) {
    397                         return;
    398                 }
    399 
    400                 if ( is_admin() || ! is_post_type_archive( 'meeting' ) ) {
    401                         return;
    402                 }
    403 
    404                 $wp_admin_bar->add_menu(
    405                         array(
    406                                 'id'    => 'edit-meetings',
    407                                 'title' => '<span class="ab-icon"></span>' . __( 'Edit Meetings', 'wporg' ),
    408                                 'href'  => admin_url( 'edit.php?post_type=meeting' ),
    409                         )
    410                 );
    411         }
    412 
    413         /**
    414          * Adds icon for the "Edit Meetings" item.
    415          */
    416         public function add_edit_meetings_icon_to_admin_bar() {
    417                 if ( ! current_user_can( 'edit_posts' ) ) {
    418                         return;
    419                 }
    420 
    421                 wp_add_inline_style( 'admin-bar', '
    422                         #wpadminbar #wp-admin-bar-edit-meetings .ab-icon:before {
    423                                 content: "\f145";
    424                                 top: 2px;
    425                         }
    426                 ' );
    427         }
    428 
    429         /**
    430          * Renders meeting information with the next meeting time based on user's local timezone. Used in Make homepage.
    431          */
    432         public function meeting_time_shortcode( $attr, $content = '' ) {
    433 
    434                 $attr = shortcode_atts( array(
    435                         'team' => null,
    436                         'limit' => 1,
    437                         'before' => __( 'Next meeting: ', 'wporg' ),
    438                         'titletag' => 'strong',
    439                         'more' => true,
    440                 ), $attr );
    441 
    442                 if ( empty( $attr['team'] ) ) {
    443                         return '';
    444                 }
    445 
    446                 if ( $attr['team'] === 'Documentation' ) {
    447                         $attr['team'] = 'Docs';
    448                 }
    449 
    450                 if ( ! has_action( 'wp_footer', array( $this, 'time_conversion_script' ) ) ) {
    451                         add_action( 'wp_footer', array( $this, 'time_conversion_script' ), 999 );
    452                 }
    453 
    454 
    455                 // meta query to eliminate expired meetings from query
    456                 add_filter( 'get_meta_sql', function ($sql) {
    457                         return str_replace( "'CURDATE()'", 'CURDATE()', $sql );
    458                 } );
    459 
    460                 switch_to_blog( get_main_site_id() );
    461 
    462                 $query = new WP_Query(
    463                         array(
    464                                 'post_type' => 'meeting',
    465                                 'nopaging'  => true,
    466                                 'meta_query' => array(
    467                                         'relation' => 'AND',
    468                                         array(
    469                                                 'key'     => 'team',
    470                                                 'value'   => $attr['team'],
    471                                                 'compare' => 'EQUALS',
    472                                         ),
    473                                         $this->meeting_meta_query
    474                                 )
    475                         )
    476                 );
    477 
    478                 $limit = $attr['limit'] > 0 ? $attr['limit'] : count( $query->posts );
    479 
    480                 $out = '';
    481                 foreach ( array_slice( $query->posts, 0, $limit ) as $post ) {
    482                         $next_meeting_datestring = $post->next_date;
    483                         $utc_time = strftime( '%H:%M:%S', strtotime( $post->time ) );
    484                         $next_meeting_iso        = $next_meeting_datestring . 'T' . $utc_time . '+00:00';
    485                         $next_meeting_timestamp = strtotime( $next_meeting_datestring . ' '. $utc_time );
    486                         $next_meeting_display = strftime( '%c %Z', $next_meeting_timestamp );
    487 
    488                         $slack_channel = null;
    489                         if ( $post->location && preg_match( '/^#([-\w]+)$/', trim( $post->location ), $match ) ) {
    490                                 $slack_channel = sanitize_title( $match[1] );
    491                         }
    492 
    493                         $out .= '<p>';
    494                         $out .= esc_html( $attr['before'] );
    495                         $out .= '<strong class="meeting-title">' . esc_html( $post->post_title ) . '</strong>';
    496                         $display_more = $query->found_posts - intval( $limit );
    497                         if ( $display_more > 0 ) {
    498                                 $out .= ' <a title="Click to view all meetings for this team" href="/meetings/#' . esc_attr( strtolower( $attr['team'] ) ) . '">' . sprintf( __( '(+%s more)'), $display_more ) . '</a>';
    499                         }
    500                         $out .= '</br>';
    501                         $out .= '<time class="date" date-time="' . esc_attr( $next_meeting_iso ) . '" title="' . esc_attr( $next_meeting_iso ) . '">' . $next_meeting_display . '</time> ';
    502                         $out .= sprintf( esc_html__( '(%s from now)' ), human_time_diff( $next_meeting_timestamp, current_time('timestamp') ) );
    503                         if ( $post->location && $slack_channel ) {
    504                                 $out .= ' ' . sprintf( wp_kses( __('at <a href="%s">%s</a> on Slack'), array(  'a' => array( 'href' => array() ) ) ), 'https://wordpress.slack.com/messages/' . $slack_channel,   $post->location );
    505                         }
    506                         $out .= '</p>';
    507                 }
    508 
    509                 restore_current_blog();
    510 
    511                 return $out;
    512         }
    513 
    514         private $meeting_meta_query = array(
    515                 'relation'=>'OR',
    516                         // not recurring  AND start_date >= CURDATE() = one-time meeting today or still in future
    517                         array(
    518                                 'relation'=>'AND',
    519                                 array(
    520                                         'key'=>'recurring',
    521                                         'value'=>array( 'weekly', 'biweekly', 'occurrence', 'monthly', '1' ),
    522                                         'compare'=>'NOT IN',
    523                                 ),
    524                                 array(
    525                                         'key'=>'start_date',
    526                                         'type'=>'DATE',
    527                                         'compare'=>'>=',
    528                                         'value'=>'CURDATE()',
    529                                 )
    530                         ),
    531                         // recurring = 1 AND ( end_date = '' OR end_date > CURDATE() ) = recurring meeting that has no end or has not ended yet
    532                         array(
    533                                 'relation'=>'AND',
    534                                 array(
    535                                         'key'=>'recurring',
    536                                         'value'=>array( 'weekly', 'biweekly', 'occurrence', 'monthly', '1' ),
    537                                         'compare'=>'IN',
    538                                 ),
    539                                 array(
    540                                         'relation'=>'OR',
    541                                         array(
    542                                                 'key'=>'end_date',
    543                                                 'value'=>'',
    544                                                 'compare'=>'=',
    545                                         ),
    546                                         array(
    547                                                 'key'=>'end_date',
    548                                                 'type'=>'DATE',
    549                                                 'compare'=>'>',
    550                                                 'value'=>'CURDATE()',
    551                                         )
    552                                 )
    553                         ),
    554                 );
    555 
    556         public function time_conversion_script() {
    557                 echo <<<EOF
    558 <script type="text/javascript">
    559 
    560         var parse_date = function (text) {
    561                 var m = /^([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})\+00:00$/.exec(text);
    562                 var d = new Date();
    563                 d.setUTCFullYear(+m[1]);
    564                 d.setUTCDate(+m[3]);
    565                 d.setUTCMonth(+m[2]-1);
    566                 d.setUTCHours(+m[4]);
    567                 d.setUTCMinutes(+m[5]);
    568                 d.setUTCSeconds(+m[6]);
    569                 return d;
    570         }
    571         var format_time = function (d) {
    572                 return d.toLocaleTimeString(navigator.language, {weekday: 'long', hour: '2-digit', minute: '2-digit', timeZoneName: 'short'});
    573         }
    574 
    575         var nodes = document.getElementsByTagName('time');
    576         for (var i=0; i<nodes.length; ++i) {
    577                 var node = nodes[i];
    578                 if (node.className === 'date') {
    579                         var d = parse_date(node.getAttribute('date-time'));
    580                         if (d) {
    581                                 node.textContent = format_time(d);
    582                         }
    583                 }
    584         }
    585 </script>
    586 EOF;
    587         }
     16if ( ! class_exists( '\WordPressdotorg\Autoload\Autoloader', false ) ) {
     17        include __DIR__ . '/vendor/wordpressdotorg/autoload/class-autoloader.php';
    58818}
    58919
    590 // fire it up
    591 Meeting_Post_Type::init();
    592 
    593 endif;
    594 
     20// Register an Autoloader for all files.
     21Autoload\register_class_path( __NAMESPACE__, __DIR__ . '/inc' );
     22Autoload\register_class_path( 'WordPressdotorg\Meetings\Common', __DIR__ . '/inc/common' );
    59523
     24// Instantiate the Plugin.
     25Plugin::get_instance();