Making WordPress.org

Ticket #3413: map_patch.diff

File map_patch.diff, 57.2 KB (added by stiofansisland, 6 years ago)

Patch to change caching to 1 day

  • functions.php

     
    1 <?php
    2 /**
    3  * WordCamp Central Functions
    4  *
    5  * (Almost) everything in this file works around the base class called WordCamp_Central_Theme,
    6  * which is a static class, and should never have an instance (hence the trigger_error trick
    7  * in the class constructor.)
    8  *
    9  */
    10 
    11 /**
    12  * WordCamp_Central_Theme Class
    13  *
    14  * Static class, used a lot throughout the WordCamp Central theme,
    15  * so please be careful when changing names, extending, etc. Everything
    16  * starts from the on_load method. The __construct method triggers an error.
    17  */
    18 class WordCamp_Central_Theme {
    19 
    20         /**
    21          * Constructor, triggers an error message.
    22          * Please use the class directly, without creating an instance.
    23          */
    24         function __construct() {
    25                 trigger_error( 'Please use class, not instance! ' . __CLASS__ );
    26         }
    27 
    28         /**
    29          * Use this class method instead of the usual constructor.
    30          * Add more actions and filters from within this method.
    31          */
    32         public static function on_load() {
    33                 add_action( 'after_setup_theme', array( __CLASS__, 'after_setup_theme' ), 11 );
    34                 add_action( 'widgets_init', array( __CLASS__, 'widgets_init' ), 11 );
    35                 add_action( 'pre_get_posts', array( __CLASS__, 'pre_get_posts' ) );
    36                 add_action( 'init', array( __CLASS__, 'process_forms' ) );
    37                 add_action( 'wp_enqueue_scripts', array( __CLASS__, 'enqueue_scripts' ) );
    38                 add_action( 'wp_ajax_get_latest_wordcamp_tweets', array( __CLASS__, 'get_latest_tweets' ) );
    39                 add_action( 'wp_ajax_nopriv_get_latest_wordcamp_tweets', array( __CLASS__, 'get_latest_tweets' ) );
    40 
    41                 add_filter( 'excerpt_more', array( __CLASS__, 'excerpt_more' ), 11 );
    42                 add_filter( 'nav_menu_css_class', array( __CLASS__, 'nav_menu_css_class' ), 10, 3 );
    43                 add_filter( 'wp_nav_menu_items', array( __CLASS__, 'add_links_to_footer_menu' ), 10, 2 );
    44 
    45                 add_shortcode( 'wcc_map',         array( __CLASS__, 'shortcode_map'         ) );
    46                 add_shortcode( 'wcc_about_stats', array( __CLASS__, 'shortcode_about_stats' ) );
    47         }
    48 
    49         /**
    50          * Fired during after_setup_theme.
    51          */
    52         static function after_setup_theme() {
    53                 add_theme_support( 'post-formats', array( 'link', 'image' ) );
    54                 $GLOBALS['custom_background'] = 'WordCamp_Central_Theme_Kill_Features';
    55                 $GLOBALS['custom_image_header'] = 'WordCamp_Central_Theme_Kill_Features';
    56 
    57                 // Add some new image sizes, also site shot is 205x148, minimap is 130x70
    58                 add_image_size( 'wccentral-thumbnail-small', 82, 37, true );
    59                 add_image_size( 'wccentral-thumbnail-large', 926, 160, true );
    60                 add_image_size( 'wccentral-thumbnail-past', 130, 60, true );
    61                 add_image_size( 'wccentral-thumbnail-hero', 493, 315, true );
    62 
    63                 // Can I haz editor style?
    64                 add_editor_style();
    65         }
    66 
    67         /**
    68          * Fired during widgets_init, removes some Twenty Ten sidebars.
    69          */
    70         static function widgets_init() {
    71                 unregister_sidebar( 'fourth-footer-widget-area' );
    72                 unregister_sidebar( 'secondary-widget-area' );
    73 
    74                 register_sidebar( array(
    75                         'name' => __( 'Pages Widget Area', 'twentyten' ),
    76                         'id' => 'pages-widget-area',
    77                         'description' => __( 'Widgets displayed on pages.', 'twentyten' ),
    78                         'before_widget' => '<li id="%1$s" class="widget-container %2$s">',
    79                         'after_widget' => '</li>',
    80                         'before_title' => '<h3 class="widget-title">',
    81                         'after_title' => '</h3>',
    82                 ) );
    83                 register_sidebar( array(
    84                         'name' => __( 'Blog Widget Area', 'twentyten' ),
    85                         'id' => 'blog-widget-area',
    86                         'description' => __( 'Widgets displayed on the blog.', 'twentyten' ),
    87                         'before_widget' => '<li id="%1$s" class="widget-container %2$s">',
    88                         'after_widget' => '</li>',
    89                         'before_title' => '<h3 class="widget-title">',
    90                         'after_title' => '</h3>',
    91                 ) );
    92         }
    93 
    94         /**
    95          * Fired during pre_get_posts, $query is passed by reference.
    96          * Removes pages and WordCamps from search results.
    97          */
    98         static function pre_get_posts( $query ) {
    99                 if ( $query->is_search && $query->is_main_query() && ! is_admin() )
    100                         $query->set( 'post_type', 'post' );
    101         }
    102 
    103         /**
    104          * Forms Processing
    105          *
    106          * Fired during init, checks REQUEST data for any submitted forms,
    107          * does the whole form processing and redirects if necessary.
    108          */
    109         static function process_forms() {
    110                 $available_actions = array( 'subscribe' );
    111                 if ( ! isset( $_REQUEST['wccentral-form-action'] ) || ! in_array( $_REQUEST['wccentral-form-action'], $available_actions ) )
    112                         return;
    113 
    114                 $action = $_REQUEST['wccentral-form-action'];
    115                 switch ( $action ) {
    116 
    117                         // Subscribe to mailing list
    118                         case 'subscribe':
    119                                 if ( ! call_user_func( array( __CLASS__, 'can_subscribe' ) ) )
    120                                         return;
    121 
    122                                 // Jetpack will do the is_email check for us
    123                                 $email = $_REQUEST['wccentral-subscribe-email'];
    124                                 $subscribe = Jetpack_Subscriptions::subscribe( $email, 0, false );
    125 
    126                                 // The following part is taken from the Jetpack subscribe widget (subscriptions.php)
    127                                 if ( is_wp_error( $subscribe ) ) {
    128                                         $error = $subscribe->get_error_code();
    129                                 } else {
    130                                         $error = false;
    131                                         foreach ( $subscribe as $response ) {
    132                                                 if ( is_wp_error( $response ) ) {
    133                                                         $error = $response->get_error_code();
    134                                                         break;
    135                                                 }
    136                                         }
    137                                 }
    138 
    139                                 if ( $error ) {
    140                                         switch( $error ) {
    141                                                 case 'invalid_email':
    142                                                         $redirect = add_query_arg( 'subscribe', 'invalid_email' );
    143                                                         break;
    144                                                 case 'active': case 'pending':
    145                                                         $redirect = add_query_arg( 'subscribe', 'already' );
    146                                                         break;
    147                                                 default:
    148                                                         $redirect = add_query_arg( 'subscribe', 'error' );
    149                                                         break;
    150                                         }
    151                                 } else {
    152                                         $redirect = add_query_arg( 'subscribe', 'success' );
    153                                 }
    154 
    155                                 wp_safe_redirect( esc_url_raw( $redirect ) );
    156                                 exit;
    157                                 break;
    158                 }
    159 
    160                 return;
    161         }
    162 
    163         /**
    164          * Enqueue scripts and styles.
    165          */
    166         static function enqueue_scripts() {
    167                 wp_enqueue_style( 'central', get_stylesheet_uri(), array(), 10 );
    168                 wp_enqueue_script( 'wordcamp-central', get_stylesheet_directory_uri() . '/js/central.js', array( 'jquery', 'underscore' ), 2, true );
    169 
    170                 wp_localize_script( 'wordcamp-central', 'wordcampCentralOptions', self::get_javascript_options() );
    171 
    172                 /* We add some JavaScript to pages with the comment form
    173                  * to support sites with threaded comments (when in use).
    174                  */
    175                 if ( is_singular() && get_option( 'thread_comments' ) ) {
    176                         wp_enqueue_script( 'comment-reply' );
    177                 }
    178 
    179                 if ( is_front_page() || is_page( 'about' ) ) {
    180                         wp_enqueue_script( 'jquery-cycle', get_stylesheet_directory_uri() . '/js/jquery.cycle.min.js', array( 'jquery' ) );
    181                 }
    182 
    183                 if ( is_page( 'about' ) || is_page( 'schedule' ) ) {
    184                         wp_enqueue_script( 'google-maps', 'https://maps.googleapis.com/maps/api/js', array(), false, true );
    185                 }
    186         }
    187 
    188         /**
    189          * Build the array of options to pass to the client side
    190          *
    191          * @return array
    192          */
    193         protected static function get_javascript_options() {
    194                 global $post;
    195 
    196                 if ( ! is_a( $post, 'WP_Post' ) ) {
    197                         return array();
    198                 }
    199 
    200                 $options = array( 'ajaxURL' => admin_url( 'admin-ajax.php' ) );
    201 
    202                 if ( $map_id = self::get_map_id( $post->post_content ) ) {
    203                         $options['mapContainer']            = "wcc-map-$map_id";
    204                         $options['markerIconBaseURL']       = get_stylesheet_directory_uri() . '/images/';
    205                         $options['markerClusterIcon']       = 'icon-marker-clustered.png';
    206                         $options['markerIconAnchorXOffset'] = 24;
    207                         $options['markerIconHeight']        = 94;
    208                         $options['markerIconWidth']         = 122;
    209 
    210                         if ( $map_markers = self::get_map_markers( $map_id ) ) {
    211                                 $options['mapMarkers'] = $map_markers;
    212                         }
    213                 }
    214 
    215                 return $options;
    216         }
    217 
    218         /**
    219          * Get the ID of the map called in the given page
    220          *
    221          * @param string $post_content
    222          *
    223          * @return mixed A string of the map name on success, or false on failure
    224          */
    225         protected static function get_map_id( $post_content ) {
    226                 $map_id = false;
    227 
    228                 if ( has_shortcode( $post_content, 'wcc_map' ) ) {
    229                         preg_match_all( '/' . get_shortcode_regex() . '/s', $post_content, $shortcodes, PREG_SET_ORDER );
    230 
    231                         foreach ( $shortcodes as $shortcode ) {
    232                                 if ( 'wcc_map' == $shortcode[2] ) {
    233                                         $attributes = shortcode_parse_atts( $shortcode[3] );
    234                                         $map_id     = sanitize_text_field( $attributes['id'] );
    235                                         break;
    236                                 }
    237                         }
    238                 }
    239 
    240                 return $map_id;
    241         }
    242 
    243         /**
    244          * Get the markers assigned to the given map
    245          *
    246          * @param string $map_id
    247          *
    248          * @return array
    249          */
    250         protected static function get_map_markers( $map_id ) {
    251                 $transient_key = "wcc_map_markers_$map_id";
    252 
    253                 if ( $markers = get_transient( $transient_key ) ) {
    254                         return $markers;
    255                 } else {
    256                         $markers = array();
    257                 }
    258 
    259                 // Get the raw marker posts for the given map
    260                 $parameters = array(
    261                         'post_type'      => 'wordcamp',
    262                         'posts_per_page' => -1,
    263                         'post_status'    => array_merge(
    264                                 WordCamp_Loader::get_public_post_statuses(),
    265                                 WordCamp_Loader::get_pre_planning_post_statuses()
    266                         ),
    267                 );
    268 
    269                 switch( $map_id ) {
    270                         case 'schedule':
    271                                 $parameters['meta_query'][] = array(
    272                                         'key'     => 'Start Date (YYYY-mm-dd)',
    273                                         'value'   => strtotime( '-2 days' ),
    274                                         'compare' => '>',
    275                                 );
    276                                 break;
    277                 }
    278 
    279                 $raw_markers = get_posts( $parameters );
    280 
    281                 // Convert the raw markers into prepared objects that are ready to be used on the JavaScript side
    282                 foreach ( $raw_markers as $marker ) {
    283                         if ( 'schedule' == $map_id ) {
    284                                 $marker_type = 'upcoming';
    285                         } else {
    286                                 $marker_type = get_post_meta( $marker->ID, 'Start Date (YYYY-mm-dd)', true ) > strtotime( '-2 days' ) ? 'upcoming' : 'past';
    287                         }
    288 
    289                         if ( ! $coordinates = get_post_meta( $marker->ID, '_venue_coordinates', true ) ) {
    290                                 continue;
    291                         }
    292 
    293                         $markers[ $marker->ID ] = array(
    294                                 'id'          => $marker->ID,
    295                                 'name'        => wcpt_get_wordcamp_title( $marker->ID ),
    296                                 'dates'       => wcpt_get_wordcamp_start_date( $marker->ID ),
    297                                 'location'    => get_post_meta( $marker->ID, 'Location', true ),
    298                                 'venueName'   => get_post_meta( $marker->ID, 'Venue Name', true ),
    299                                 'url'         => self::get_best_wordcamp_url( $marker->ID ),
    300                                 'latitude'    => $coordinates['latitude'],
    301                                 'longitude'   => $coordinates['longitude'],
    302                                 'iconURL'     => "icon-marker-{$marker_type}-2x.png",
    303                         );
    304                 }
    305 
    306                 $markers = apply_filters( 'wcc_get_map_markers', $markers );
    307 
    308                 set_transient( $transient_key, $markers, WEEK_IN_SECONDS );
    309                         // todo this should probably be changed to just DAY_IN_SECONDS to avoid confusion among organizers. -- https://wordpress.slack.com/archives/meta-wordcamp/p1477323414000597
    310                         // need to understand why it was set for so long in the first place, and test change
    311                         // should just always display cached data and have cron job to refresh asyncronously
    312 
    313                 return $markers;
    314         }
    315 
    316         /**
    317          * Filters excerpt_more.
    318          */
    319         static function excerpt_more( $more ) {
    320                 return '&nbsp;&hellip;';
    321         }
    322 
    323         /**
    324          * Filters nav_menu_css_class.
    325          * Make sure Schedule is current-menu-item when viewing WordCamps.
    326          */
    327         static function nav_menu_css_class( $classes, $item, $args ) {
    328                 if ( 'wordcamp' == get_post_type() ) {
    329                         if ( home_url( '/schedule/' ) == trailingslashit( $item->url ) ) {
    330                                 $classes[] = 'current-menu-item';
    331                         } else {
    332                                 $remove = array( 'current-menu-item', 'current_page_parent', 'current_page_ancestor' );
    333                                 foreach ( $remove as $class )
    334                                         $classes = array_splice( $classes, array_search( $class, $classes ), 1 );
    335                         }
    336                 }
    337                 return $classes;
    338         }
    339 
    340         public static function add_links_to_footer_menu( $items, $args ) {
    341                 if ( 'menu-footer' == $args->container_class ) {
    342                         ob_start();
    343 
    344                         ?>
    345 
    346                         <li class="menu-item menu-item-type-custom menu-item-object-custom"><a href="<?php echo esc_url( get_feed_link() ); ?>">RSS (posts)</a></li>
    347                         <li class="menu-item menu-item-type-custom menu-item-object-custom"><a href="<?php echo esc_url( get_post_type_archive_feed_link( 'wordcamp' ) ); ?>">RSS (WordCamps)</a></li>
    348                         <li class="menu-item menu-item-type-custom menu-item-object-custom"><a href="https://wordpress.org/about/privacy/">Privacy Policy</a></li>
    349 
    350                         <?php
    351                         $items .= ob_get_clean();
    352                 }
    353 
    354                 return $items;
    355         }
    356 
    357         /**
    358          * Get Session List
    359          *
    360          * Uses the WordCamp post type to loop through the latest
    361          * WordCamps, if WordCamp URLs are valid network blogs, switches
    362          * to blog and queries for Session.
    363          *
    364          * @uses switch_to_blog, get_blog_details, wp_object_cache
    365          * @return assoc array with session and WC info
    366          */
    367         public static function get_sessions( $count = 4 ) {
    368                 if ( ! function_exists( 'wcpt_has_wordcamps' ) )
    369                         return false;
    370 
    371                 // Check cache
    372                 if ( (bool) $sessions = wp_cache_get( 'wccentral_sessions_' . $count ) )
    373                         return $sessions;
    374 
    375                 // Take latest WordCamps
    376                 $args = array(
    377                         'posts_per_page' => $count + 10,
    378                         'meta_key'       => 'Start Date (YYYY-mm-dd)',
    379                         'orderby'        => 'meta_value',
    380                         'order'          => 'ASC',
    381                         'meta_query'     => array( array(
    382                                 'key'        => 'Start Date (YYYY-mm-dd)',
    383                                 'value'      => strtotime( '-2 days' ),
    384                                 'compare'    => '>'
    385                         ) )
    386                 );
    387 
    388                 if ( ! wcpt_has_wordcamps( $args ) )
    389                         return false;
    390 
    391                 // We'll hold the sessions here
    392                 $sessions = array();
    393 
    394                 // Loop through the latest WCs
    395                 while ( wcpt_wordcamps() ) {
    396                         wcpt_the_wordcamp();
    397 
    398                         // Store WC data (will be unavailable after switch_to_blog)
    399                         $domain = parse_url( wcpt_get_wordcamp_url(), PHP_URL_HOST );
    400                         $blog_details = get_blog_details( array( 'domain' => $domain ), false );
    401 
    402                         $wordcamp_date = wcpt_get_wordcamp_start_date( 0, 'F ' );
    403                         $wordcamp_date .= wcpt_get_wordcamp_start_date( 0, 'j' );
    404                         if ( wcpt_get_wordcamp_end_date( 0, 'j' ) )
    405                                 $wordcamp_date .= '-' . wcpt_get_wordcamp_end_date( 0, 'j' );
    406 
    407                         // Valid for all sessions in this WC
    408                         $session = array(
    409                                 'wordcamp_title' => wcpt_get_wordcamp_title(),
    410                                 'wordcamp_permalink' => wcpt_get_wordcamp_permalink(),
    411                                 'wordcamp_date' => $wordcamp_date,
    412                                 'wordcamp_thumb' => get_the_post_thumbnail( get_the_ID(), 'wccentral-thumbnail-small' ),
    413                         );
    414 
    415                         if ( isset( $blog_details->blog_id ) && $blog_details->blog_id ) {
    416                                 $my_blog_id = (int) $blog_details->blog_id;
    417 
    418                                 switch_to_blog( $my_blog_id );
    419 
    420                                         // Look through 5 sessions, store in $sessions array
    421                                         $sessions_query = new WP_Query( array( 'post_type' => 'wcb_session', 'posts_per_page' => 5, 'post_status' => 'publish' ) );
    422                                         while ( $sessions_query->have_posts() ) {
    423                                                 $sessions_query->the_post();
    424 
    425                                                 // Add the extra fields to $session and push to $sessions
    426                                                 $sessions[] = array_merge( $session, array(
    427                                                         'name' => apply_filters( 'the_title', get_the_title() ),
    428                                                         'speakers' => get_post_meta( get_the_ID(), '_wcb_session_speakers', true ),
    429                                                         'permalink' => get_permalink( get_the_ID() ),
    430                                                 ) );
    431                                         }
    432 
    433                                 restore_current_blog();
    434                         }
    435                 }
    436 
    437                 // Randomize and pick $count
    438                 shuffle( $sessions );
    439                 $sessions = array_slice( $sessions, 0, $count );
    440 
    441                 // Cache in transients
    442                 wp_cache_set( 'wccentral_sessions_' . $count, $sessions );
    443                 return $sessions;
    444         }
    445 
    446         /**
    447          * Retrieve Subscription Status from $_REQUEST
    448          */
    449         public static function get_subscription_status() {
    450                 return isset( $_REQUEST['subscribe'] ) ? strtolower( $_REQUEST['subscribe'] ) : false;
    451         }
    452 
    453         /**
    454          * Subscription Check
    455          * Returns true if subscriptions are available
    456          */
    457         public static function can_subscribe() {
    458                 return class_exists( 'Jetpack_Subscriptions' ) && is_callable( array( 'Jetpack_Subscriptions', 'subscribe' ) );
    459         }
    460 
    461         /**
    462          * Fetch the latest tweets from the @WordCamp account
    463          *
    464          * This is an AJAX callback returning JSON-formatted data.
    465          *
    466          * We're manually expiring/refreshing the transient to ensure that we only ever update it when we have a
    467          * valid response from the API. If there is a problem retrieving new data from the API, then we want to
    468          * continue displaying the valid cached data until we can successfully retrieve new data. The data is still
    469          * stored in a transient instead of an option, though, so that it can be cached in memory.
    470          *
    471          * Under certain unlikely conditions, this could cause an API rate limit violation. If the data is expired
    472          * and we can connect to the API at the network level, but then the request fails at the application level
    473          * (invalid credentials, etc), then we'll be hitting the API every time this function is called. If that
    474          * does ever happen, it could be fixed by setting the timestamp of the last attempt in a transient and only
    475          * issuing another attempt if ~2 minutes have passed.
    476          */
    477         public static function get_latest_tweets() {
    478                 $transient_key = 'wcc_latest_tweets';
    479                 $tweets        = get_transient( $transient_key );
    480                 $expired       = $tweets['last_update'] < strtotime( 'now - 15 minutes' );
    481 
    482                 if ( ! $tweets || $expired ) {
    483                         $response = wp_remote_get(
    484                                 'https://api.twitter.com/1.1/statuses/user_timeline.json?count=6&trim_user=true&exclude_replies=true&include_rts=false&screen_name=wordcamp',
    485                                 array(
    486                                         'headers' => array( 'Authorization' => 'Bearer ' . TWITTER_BEARER_TOKEN_WORDCAMP_CENTRAL ),
    487                                 )
    488                         );
    489 
    490                         if ( ! is_wp_error( $response ) ) {
    491                                 $tweets['tweets'] = json_decode( wp_remote_retrieve_body( $response ) );
    492 
    493                                 /*
    494                                  * Remove all but the first 3 tweets
    495                                  *
    496                                  * The Twitter API includes retweets in the `count` parameter, even if include_rts=false is passed,
    497                                  * so we have to request more tweets than we actually want and then cut it down here.
    498                                  */
    499                                 if ( is_array( $tweets['tweets'] ) ) {
    500                                         $tweets['tweets']      = array_slice( $tweets['tweets'], 0, 3 );
    501                                         $tweets['tweets']      = self::sanitize_format_tweets( $tweets['tweets'] );
    502                                         $tweets['last_update'] = time();
    503 
    504                                         set_transient( $transient_key, $tweets );
    505                                 }
    506                         }
    507                 }
    508 
    509                 wp_send_json_success( $tweets );
    510         }
    511 
    512         /**
    513          * Sanitize and format the tweet objects
    514          *
    515          * Whitelist the fields to cut down on how much data we're storing/transmitting, but also to force
    516          * future devs to manually enable/sanitize any new fields that are used, which avoids the risk of
    517          * accidentally using an unsafe value.
    518          *
    519          * @param array $tweets
    520          *
    521          * @return array
    522          */
    523         protected static function sanitize_format_tweets( $tweets ) {
    524                 $whitelisted_fields = array( 'id_str' => '', 'text' => '', 'created_at' => '' );
    525 
    526                 foreach ( $tweets as & $tweet ) {
    527                         $tweet           = (object) shortcode_atts( $whitelisted_fields, $tweet );
    528                         $tweet->id_str   = sanitize_text_field( $tweet->id_str );
    529                         $tweet->text     = wp_kses( $tweet->text, wp_kses_allowed_html( 'data' ), array( 'http', 'https', 'mailto' ) );
    530                         $tweet->text     = make_clickable( $tweet->text );
    531                         $tweet->text     = self::link_hashtags_and_usernames( $tweet->text );
    532                         $tweet->time_ago = human_time_diff( strtotime( $tweet->created_at ) );
    533                 }
    534 
    535                 return $tweets;
    536         }
    537 
    538         /**
    539          * Convert usernames and hashtags to links
    540          *
    541          * Based on Tagregator's TGGRSourceTwitter::link_hashtags_and_usernames().
    542          *
    543          * @param string $text
    544          *
    545          * @return string
    546          */
    547         protected static function link_hashtags_and_usernames( $content ) {
    548                 $content = preg_replace( '/@(\w+)/',       '<a href="https://twitter.com/\\1"          class="wc-tweets-username">@\\1</a>', $content );
    549                 $content = preg_replace( '/(?<!&)#(\w+)/', '<a href="https://twitter.com/search?q=\\1" class="wc-tweets-tag"     >#\\1</a>', $content );
    550 
    551                 return $content;
    552         }
    553 
    554         /**
    555          * Twenty Ten Comment
    556          * Overrides the twentyten_comment function in the parent theme.
    557          */
    558         public static function twentyten_comment( $comment, $args, $depth ) {
    559                 $GLOBALS['comment'] = $comment;
    560                 switch ( $comment->comment_type ) :
    561                         case '' :
    562                 ?>
    563                 <li <?php comment_class(); ?> id="li-comment-<?php comment_ID(); ?>">
    564                         <div id="comment-<?php comment_ID(); ?>" class="comment-container">
    565                         <div class="comment-author vcard">
    566                                 <?php echo get_avatar( $comment, 60 ); ?>
    567                                 <?php printf( __( '%s <span class="says">says:</span>', 'twentyten' ), sprintf( '<cite class="fn">%s</cite>', get_comment_author_link() ) ); ?>
    568                         </div><!-- .comment-author .vcard -->
    569                         <?php if ( $comment->comment_approved == '0' ) : ?>
    570                                 <em class="comment-awaiting-moderation"><?php _e( 'Your comment is awaiting moderation.', 'twentyten' ); ?></em>
    571                                 <br />
    572                         <?php endif; ?>
    573 
    574                         <div class="comment-meta commentmetadata"><a href="<?php echo esc_url( get_comment_link( $comment->comment_ID ) ); ?>">
    575                                 <?php
    576                                         /* translators: 1: date, 2: time */
    577                                         printf( __( '%1$s at %2$s', 'twentyten' ), get_comment_date(),  get_comment_time() ); ?></a><?php edit_comment_link( __( '(Edit)', 'twentyten' ), ' ' );
    578                                 ?>
    579                         </div><!-- .comment-meta .commentmetadata -->
    580 
    581                         <div class="comment-body"><?php comment_text(); ?></div>
    582 
    583                         <div class="reply">
    584                                 <?php comment_reply_link( array_merge( $args,
    585                                         array(
    586                                                 'depth' => $depth,
    587                                                 'max_depth' => $args['max_depth'],
    588                                                 'reply_text' => '&#10149; Reply'
    589                                         )
    590                                 ) ); ?>
    591                         </div><!-- .reply -->
    592                 </div><!-- #comment-##  -->
    593 
    594                 <?php
    595                                 break;
    596                         case 'pingback'  :
    597                         case 'trackback' :
    598                 ?>
    599                 <li class="post pingback">
    600                         <p><?php _e( 'Pingback:', 'twentyten' ); ?> <?php comment_author_link(); ?><?php edit_comment_link( __( '(Edit)', 'twentyten' ), ' ' ); ?></p>
    601                 <?php
    602                                 break;
    603                 endswitch;
    604         }
    605 
    606         public static function get_upcoming_wordcamps_query( $count = 10 ) {
    607                 $query = new WP_Query( array(
    608                         'post_type'              => WCPT_POST_TYPE_ID,
    609                         'post_status'    => WordCamp_Loader::get_public_post_statuses(),
    610                         'posts_per_page' => $count,
    611                         'meta_key'       => 'Start Date (YYYY-mm-dd)',
    612                         'orderby'        => 'meta_value',
    613                         'order'          => 'ASC',
    614                         'meta_query'     => array( array(
    615                                 'key'        => 'Start Date (YYYY-mm-dd)',
    616                                 'value'      => strtotime( '-2 days' ),
    617                                 'compare'    => '>'
    618                         ) )
    619                 ) );
    620                 return $query;
    621         }
    622 
    623         /**
    624          * Output the date or date range of a WordCamp event.
    625          *
    626          * @param int  $wordcamp_id The ID of the WordCamp post.
    627          * @param bool $show_year   Optional. True to include the year in the date output.
    628          */
    629         public static function the_wordcamp_date( $wordcamp_id = 0, $show_year = false ) {
    630                 $start_day = wcpt_get_wordcamp_start_date( $wordcamp_id, 'j' );
    631                 $start_month = wcpt_get_wordcamp_start_date( $wordcamp_id, 'F' );
    632                 $end_day = wcpt_get_wordcamp_end_date( $wordcamp_id, 'j' );
    633                 $end_month = wcpt_get_wordcamp_end_date( $wordcamp_id, 'F' );
    634 
    635                 if ( $show_year ) {
    636                         $start_year = wcpt_get_wordcamp_start_date( $wordcamp_id, 'Y' );
    637                         $end_year   = wcpt_get_wordcamp_end_date( $wordcamp_id, 'Y' );
    638                 }
    639 
    640                 echo "$start_month $start_day";
    641 
    642                 if ( $end_day ) {
    643                         if ( $show_year && $start_year !== $end_year ) {
    644                                 echo ", $start_year";
    645                         }
    646 
    647                         echo '&ndash;';
    648 
    649                         if ( $start_month !== $end_month ) {
    650                                 echo "$end_month ";
    651                         }
    652 
    653                         echo $end_day;
    654 
    655                         if ( $show_year ) {
    656                                 echo ", $end_year";
    657                         }
    658                 } elseif ( $show_year ) {
    659                         echo ", $start_year";
    660                 }
    661         }
    662 
    663         /**
    664          * Group an array of WordCamps by year
    665          *
    666          * @param array $wordcamps
    667          *
    668          * @return array
    669          */
    670         public static function group_wordcamps_by_year( $wordcamps ) {
    671                 $grouped_wordcamps = array();
    672 
    673                 foreach ( $wordcamps as $wordcamp ) {
    674                         $date = get_post_meta( $wordcamp->ID, 'Start Date (YYYY-mm-dd)', true );
    675 
    676                         if ( $date && $year = date( 'Y', (int) $date ) ) {
    677                                 $grouped_wordcamps[ $year ][] = $wordcamp;
    678                         }
    679                 }
    680 
    681                 return $grouped_wordcamps;
    682         }
    683 
    684         /**
    685          * Returns a WordCamp's website URL if it's available, or their Central page if is isn't.
    686          *
    687          * @param int $post_id
    688          *
    689          * @return string
    690          */
    691         public static function get_best_wordcamp_url( $post_id = 0 ) {
    692                 $url = wcpt_get_wordcamp_url( $post_id );
    693 
    694                 if ( ! filter_var( $url, FILTER_VALIDATE_URL ) ) {
    695                         $url = wcpt_get_wordcamp_permalink( $post_id );
    696                 }
    697 
    698                 return $url;
    699         }
    700 
    701         /**
    702          * Render the [wcc_map] shortcode
    703          *
    704          * @param array $attributes
    705          *
    706          * @return string
    707          */
    708         public static function shortcode_map( $attributes ) {
    709                 $attributes = shortcode_atts( array( 'id' => '' ), $attributes );
    710 
    711                 ob_start();
    712                 require( __DIR__ . '/shortcode-about-map.php' );
    713                 return ob_get_clean();
    714         }
    715 
    716         /**
    717          * Render the [wcc_about_stats] shortcode
    718          *
    719          * @param array $attributes
    720          *
    721          * @return string
    722          */
    723         public static function shortcode_about_stats( $attributes ) {
    724                 // Allow stat values to be overridden with shortcode attributes
    725             $map_stats = shortcode_atts( self::get_map_stats(), $attributes, 'wcc_about_stats' );
    726 
    727             // Sanitize stat values
    728         $map_stats = array_map( 'absint', $map_stats );
    729 
    730                 ob_start();
    731                 require( __DIR__ . '/shortcode-about-stats.php' );
    732                 return ob_get_clean();
    733         }
    734 
    735         /**
    736          * Gather the stats for the [wcc_about_stats] shortcode
    737          *
    738          * There isn't an easy way to collect the country stats programmatically, but it just takes a minute to
    739          * manually count the number countries on the map that have pins.
    740          *
    741          * @return array
    742          */
    743         protected static function get_map_stats() {
    744                 $transient_key = 'wcc_about_map_stats';
    745 
    746                 if ( ! $map_stats = get_transient( $transient_key ) ) {
    747                         $cities    = array();
    748                         $wordcamps = new WP_Query( array(
    749                                 'post_type'      => 'wordcamp',
    750                                 'post_status'    => WordCamp_Loader::get_public_post_statuses(),
    751                                 'posts_per_page' => -1,
    752                         ) );
    753 
    754                         // Count the number of cities
    755                         // @todo use _venue_city field since it'll be more accurate, but need to populate older camps first
    756                         foreach ( $wordcamps->posts as $wordcamp ) {
    757                                 $url = get_post_meta( $wordcamp->ID, 'URL', true );
    758 
    759                                 if ( $hostname = parse_url( $url, PHP_URL_HOST ) ) {
    760                                         $city = explode( '.', $hostname );
    761                                         $cities[ $city[0] ] = true;
    762                                 }
    763                         }
    764 
    765                         // @todo generate countries automatically from _venue_country_code field, but need to populate older camps first
    766 
    767                         // Compile the results
    768                         $map_stats = array(
    769                                 'wordcamps'  => $wordcamps->found_posts,
    770                                 'cities'     => count( $cities ),
    771                                 'countries'  => 65,
    772                                 'continents' => 6,
    773                         );
    774 
    775                         set_transient( $transient_key, $map_stats, 2 * WEEK_IN_SECONDS );
    776                 }
    777 
    778                 return $map_stats;
    779         }
    780 
    781         /**
    782          * Get T-shirt Sizes, a caching wrapper for _get_tshirt_sizes.
    783          *
    784          * @param int $wordcamp_id The WordCamp post ID.
    785          *
    786          * @return array An array of sizes.
    787          */
    788         public static function get_tshirt_sizes( $wordcamp_id ) {
    789                 // TODO: Implement some caching.
    790                 $sizes = self::_get_tshirt_sizes( $wordcamp_id );
    791                 return $sizes;
    792         }
    793 
    794         /**
    795          * Get T-shirt Sizes.
    796          *
    797          * @param int $wordcamp_id The WordCamp post ID.
    798          *
    799          * @return array An array of sizes.
    800          */
    801         private static function _get_tshirt_sizes( $wordcamp_id ) {
    802                 $wordcamp = get_post( $wordcamp_id );
    803                 $sizes = array();
    804 
    805                 $wordcamp_site_id = absint( get_wordcamp_site_id( $wordcamp ) );
    806                 if ( ! $wordcamp_site_id )
    807                         return $sizes;
    808 
    809                 wp_suspend_cache_addition( true );
    810                 switch_to_blog( $wordcamp_site_id );
    811 
    812                 $questions = get_posts( array(
    813                         'post_type' => 'tix_question',
    814                         'post_status' => 'publish',
    815                         'posts_per_page' => 100,
    816                         'fields' => 'ids',
    817                 ) );
    818 
    819                 // Aggregate only t-shirt questions.
    820                 $tshirt_questions = array();
    821                 foreach ( $questions as $question_id ) {
    822                         if ( get_post_meta( $question_id, 'tix_type', true ) != 'tshirt' )
    823                                 continue;
    824 
    825                         $tshirt_questions[] = $question_id;
    826                 }
    827 
    828                 $paged = 1;
    829                 while ( $attendees = get_posts( array(
    830                         'post_type' => 'tix_attendee',
    831                         'post_status' => array( 'publish', 'pending' ),
    832                         'posts_per_page' => 200,
    833                         'paged' => $paged++,
    834                         'orderby' => 'ID',
    835                         'order' => 'ASC',
    836                         'fields' => 'ids',
    837                 ) ) ) {
    838                         foreach ( $attendees as $attendee_id ) {
    839                                 $answers = get_post_meta( $attendee_id, 'tix_questions', true );
    840                                 foreach ( $answers as $question_id => $answer ) {
    841                                         if ( in_array( $question_id, $tshirt_questions ) ) {
    842                                                 if ( ! isset( $sizes[ $answer ] ) )
    843                                                         $sizes[ $answer ] = 0;
    844 
    845                                                 $sizes[ $answer ]++;
    846                                         }
    847                                 }
    848                         }
    849                 }
    850 
    851                 restore_current_blog();
    852                 wp_suspend_cache_addition( false );
    853                 arsort( $sizes );
    854                 return $sizes;
    855         }
    856 }
    857 
    858 // Load the theme class, this is where it all starts.
    859 WordCamp_Central_Theme::on_load();
    860 
    861 // Override the parent's comment function with ours.
    862 function twentyten_comment( $comment, $args, $depth ) {
    863         return WordCamp_Central_Theme::twentyten_comment( $comment, $args, $depth );
    864 }
    865 
    866 // This class is used to kill header images and custom background added by 2010.
    867 class WordCamp_Central_Theme_Kill_Features { function init() { return false; } }
     1Index: functions.php
     2===================================================================
     3--- functions.php       (revision 6489)
     4+++ functions.php       (working copy)
     5@@ -1,867 +0,0 @@
     6-<?php
     7-/**
     8- * WordCamp Central Functions
     9- *
     10- * (Almost) everything in this file works around the base class called WordCamp_Central_Theme,
     11- * which is a static class, and should never have an instance (hence the trigger_error trick
     12- * in the class constructor.)
     13- *
     14- */
     15-
     16-/**
     17- * WordCamp_Central_Theme Class
     18- *
     19- * Static class, used a lot throughout the WordCamp Central theme,
     20- * so please be careful when changing names, extending, etc. Everything
     21- * starts from the on_load method. The __construct method triggers an error.
     22- */
     23-class WordCamp_Central_Theme {
     24-
     25-       /**
     26-        * Constructor, triggers an error message.
     27-        * Please use the class directly, without creating an instance.
     28-        */
     29-       function __construct() {
     30-               trigger_error( 'Please use class, not instance! ' . __CLASS__ );
     31-       }
     32-
     33-       /**
     34-        * Use this class method instead of the usual constructor.
     35-        * Add more actions and filters from within this method.
     36-        */
     37-       public static function on_load() {
     38-               add_action( 'after_setup_theme', array( __CLASS__, 'after_setup_theme' ), 11 );
     39-               add_action( 'widgets_init', array( __CLASS__, 'widgets_init' ), 11 );
     40-               add_action( 'pre_get_posts', array( __CLASS__, 'pre_get_posts' ) );
     41-               add_action( 'init', array( __CLASS__, 'process_forms' ) );
     42-               add_action( 'wp_enqueue_scripts', array( __CLASS__, 'enqueue_scripts' ) );
     43-               add_action( 'wp_ajax_get_latest_wordcamp_tweets', array( __CLASS__, 'get_latest_tweets' ) );
     44-               add_action( 'wp_ajax_nopriv_get_latest_wordcamp_tweets', array( __CLASS__, 'get_latest_tweets' ) );
     45-
     46-               add_filter( 'excerpt_more', array( __CLASS__, 'excerpt_more' ), 11 );
     47-               add_filter( 'nav_menu_css_class', array( __CLASS__, 'nav_menu_css_class' ), 10, 3 );
     48-               add_filter( 'wp_nav_menu_items', array( __CLASS__, 'add_links_to_footer_menu' ), 10, 2 );
     49-
     50-               add_shortcode( 'wcc_map',         array( __CLASS__, 'shortcode_map'         ) );
     51-               add_shortcode( 'wcc_about_stats', array( __CLASS__, 'shortcode_about_stats' ) );
     52-       }
     53-
     54-       /**
     55-        * Fired during after_setup_theme.
     56-        */
     57-       static function after_setup_theme() {
     58-               add_theme_support( 'post-formats', array( 'link', 'image' ) );
     59-               $GLOBALS['custom_background'] = 'WordCamp_Central_Theme_Kill_Features';
     60-               $GLOBALS['custom_image_header'] = 'WordCamp_Central_Theme_Kill_Features';
     61-
     62-               // Add some new image sizes, also site shot is 205x148, minimap is 130x70
     63-               add_image_size( 'wccentral-thumbnail-small', 82, 37, true );
     64-               add_image_size( 'wccentral-thumbnail-large', 926, 160, true );
     65-               add_image_size( 'wccentral-thumbnail-past', 130, 60, true );
     66-               add_image_size( 'wccentral-thumbnail-hero', 493, 315, true );
     67-
     68-               // Can I haz editor style?
     69-               add_editor_style();
     70-       }
     71-
     72-       /**
     73-        * Fired during widgets_init, removes some Twenty Ten sidebars.
     74-        */
     75-       static function widgets_init() {
     76-               unregister_sidebar( 'fourth-footer-widget-area' );
     77-               unregister_sidebar( 'secondary-widget-area' );
     78-
     79-               register_sidebar( array(
     80-                       'name' => __( 'Pages Widget Area', 'twentyten' ),
     81-                       'id' => 'pages-widget-area',
     82-                       'description' => __( 'Widgets displayed on pages.', 'twentyten' ),
     83-                       'before_widget' => '<li id="%1$s" class="widget-container %2$s">',
     84-                       'after_widget' => '</li>',
     85-                       'before_title' => '<h3 class="widget-title">',
     86-                       'after_title' => '</h3>',
     87-               ) );
     88-               register_sidebar( array(
     89-                       'name' => __( 'Blog Widget Area', 'twentyten' ),
     90-                       'id' => 'blog-widget-area',
     91-                       'description' => __( 'Widgets displayed on the blog.', 'twentyten' ),
     92-                       'before_widget' => '<li id="%1$s" class="widget-container %2$s">',
     93-                       'after_widget' => '</li>',
     94-                       'before_title' => '<h3 class="widget-title">',
     95-                       'after_title' => '</h3>',
     96-               ) );
     97-       }
     98-
     99-       /**
     100-        * Fired during pre_get_posts, $query is passed by reference.
     101-        * Removes pages and WordCamps from search results.
     102-        */
     103-       static function pre_get_posts( $query ) {
     104-               if ( $query->is_search && $query->is_main_query() && ! is_admin() )
     105-                       $query->set( 'post_type', 'post' );
     106-       }
     107-
     108-       /**
     109-        * Forms Processing
     110-        *
     111-        * Fired during init, checks REQUEST data for any submitted forms,
     112-        * does the whole form processing and redirects if necessary.
     113-        */
     114-       static function process_forms() {
     115-               $available_actions = array( 'subscribe' );
     116-               if ( ! isset( $_REQUEST['wccentral-form-action'] ) || ! in_array( $_REQUEST['wccentral-form-action'], $available_actions ) )
     117-                       return;
     118-
     119-               $action = $_REQUEST['wccentral-form-action'];
     120-               switch ( $action ) {
     121-
     122-                       // Subscribe to mailing list
     123-                       case 'subscribe':
     124-                               if ( ! call_user_func( array( __CLASS__, 'can_subscribe' ) ) )
     125-                                       return;
     126-
     127-                               // Jetpack will do the is_email check for us
     128-                               $email = $_REQUEST['wccentral-subscribe-email'];
     129-                               $subscribe = Jetpack_Subscriptions::subscribe( $email, 0, false );
     130-
     131-                               // The following part is taken from the Jetpack subscribe widget (subscriptions.php)
     132-                               if ( is_wp_error( $subscribe ) ) {
     133-                                       $error = $subscribe->get_error_code();
     134-                               } else {
     135-                                       $error = false;
     136-                                       foreach ( $subscribe as $response ) {
     137-                                               if ( is_wp_error( $response ) ) {
     138-                                                       $error = $response->get_error_code();
     139-                                                       break;
     140-                                               }
     141-                                       }
     142-                               }
     143-
     144-                               if ( $error ) {
     145-                                       switch( $error ) {
     146-                                               case 'invalid_email':
     147-                                                       $redirect = add_query_arg( 'subscribe', 'invalid_email' );
     148-                                                       break;
     149-                                               case 'active': case 'pending':
     150-                                                       $redirect = add_query_arg( 'subscribe', 'already' );
     151-                                                       break;
     152-                                               default:
     153-                                                       $redirect = add_query_arg( 'subscribe', 'error' );
     154-                                                       break;
     155-                                       }
     156-                               } else {
     157-                                       $redirect = add_query_arg( 'subscribe', 'success' );
     158-                               }
     159-
     160-                               wp_safe_redirect( esc_url_raw( $redirect ) );
     161-                               exit;
     162-                               break;
     163-               }
     164-
     165-               return;
     166-       }
     167-
     168-       /**
     169-        * Enqueue scripts and styles.
     170-        */
     171-       static function enqueue_scripts() {
     172-               wp_enqueue_style( 'central', get_stylesheet_uri(), array(), 10 );
     173-               wp_enqueue_script( 'wordcamp-central', get_stylesheet_directory_uri() . '/js/central.js', array( 'jquery', 'underscore' ), 2, true );
     174-
     175-               wp_localize_script( 'wordcamp-central', 'wordcampCentralOptions', self::get_javascript_options() );
     176-
     177-               /* We add some JavaScript to pages with the comment form
     178-                * to support sites with threaded comments (when in use).
     179-                */
     180-               if ( is_singular() && get_option( 'thread_comments' ) ) {
     181-                       wp_enqueue_script( 'comment-reply' );
     182-               }
     183-
     184-               if ( is_front_page() || is_page( 'about' ) ) {
     185-                       wp_enqueue_script( 'jquery-cycle', get_stylesheet_directory_uri() . '/js/jquery.cycle.min.js', array( 'jquery' ) );
     186-               }
     187-
     188-               if ( is_page( 'about' ) || is_page( 'schedule' ) ) {
     189-                       wp_enqueue_script( 'google-maps', 'https://maps.googleapis.com/maps/api/js', array(), false, true );
     190-               }
     191-       }
     192-
     193-       /**
     194-        * Build the array of options to pass to the client side
     195-        *
     196-        * @return array
     197-        */
     198-       protected static function get_javascript_options() {
     199-               global $post;
     200-
     201-               if ( ! is_a( $post, 'WP_Post' ) ) {
     202-                       return array();
     203-               }
     204-
     205-               $options = array( 'ajaxURL' => admin_url( 'admin-ajax.php' ) );
     206-
     207-               if ( $map_id = self::get_map_id( $post->post_content ) ) {
     208-                       $options['mapContainer']            = "wcc-map-$map_id";
     209-                       $options['markerIconBaseURL']       = get_stylesheet_directory_uri() . '/images/';
     210-                       $options['markerClusterIcon']       = 'icon-marker-clustered.png';
     211-                       $options['markerIconAnchorXOffset'] = 24;
     212-                       $options['markerIconHeight']        = 94;
     213-                       $options['markerIconWidth']         = 122;
     214-
     215-                       if ( $map_markers = self::get_map_markers( $map_id ) ) {
     216-                               $options['mapMarkers'] = $map_markers;
     217-                       }
     218-               }
     219-
     220-               return $options;
     221-       }
     222-
     223-       /**
     224-        * Get the ID of the map called in the given page
     225-        *
     226-        * @param string $post_content
     227-        *
     228-        * @return mixed A string of the map name on success, or false on failure
     229-        */
     230-       protected static function get_map_id( $post_content ) {
     231-               $map_id = false;
     232-
     233-               if ( has_shortcode( $post_content, 'wcc_map' ) ) {
     234-                       preg_match_all( '/' . get_shortcode_regex() . '/s', $post_content, $shortcodes, PREG_SET_ORDER );
     235-
     236-                       foreach ( $shortcodes as $shortcode ) {
     237-                               if ( 'wcc_map' == $shortcode[2] ) {
     238-                                       $attributes = shortcode_parse_atts( $shortcode[3] );
     239-                                       $map_id     = sanitize_text_field( $attributes['id'] );
     240-                                       break;
     241-                               }
     242-                       }
     243-               }
     244-
     245-               return $map_id;
     246-       }
     247-
     248-       /**
     249-        * Get the markers assigned to the given map
     250-        *
     251-        * @param string $map_id
     252-        *
     253-        * @return array
     254-        */
     255-       protected static function get_map_markers( $map_id ) {
     256-               $transient_key = "wcc_map_markers_$map_id";
     257-
     258-               if ( $markers = get_transient( $transient_key ) ) {
     259-                       return $markers;
     260-               } else {
     261-                       $markers = array();
     262-               }
     263-
     264-               // Get the raw marker posts for the given map
     265-               $parameters = array(
     266-                       'post_type'      => 'wordcamp',
     267-                       'posts_per_page' => -1,
     268-                       'post_status'    => array_merge(
     269-                               WordCamp_Loader::get_public_post_statuses(),
     270-                               WordCamp_Loader::get_pre_planning_post_statuses()
     271-                       ),
     272-               );
     273-
     274-               switch( $map_id ) {
     275-                       case 'schedule':
     276-                               $parameters['meta_query'][] = array(
     277-                                       'key'     => 'Start Date (YYYY-mm-dd)',
     278-                                       'value'   => strtotime( '-2 days' ),
     279-                                       'compare' => '>',
     280-                               );
     281-                               break;
     282-               }
     283-
     284-               $raw_markers = get_posts( $parameters );
     285-
     286-               // Convert the raw markers into prepared objects that are ready to be used on the JavaScript side
     287-               foreach ( $raw_markers as $marker ) {
     288-                       if ( 'schedule' == $map_id ) {
     289-                               $marker_type = 'upcoming';
     290-                       } else {
     291-                               $marker_type = get_post_meta( $marker->ID, 'Start Date (YYYY-mm-dd)', true ) > strtotime( '-2 days' ) ? 'upcoming' : 'past';
     292-                       }
     293-
     294-                       if ( ! $coordinates = get_post_meta( $marker->ID, '_venue_coordinates', true ) ) {
     295-                               continue;
     296-                       }
     297-
     298-                       $markers[ $marker->ID ] = array(
     299-                               'id'          => $marker->ID,
     300-                               'name'        => wcpt_get_wordcamp_title( $marker->ID ),
     301-                               'dates'       => wcpt_get_wordcamp_start_date( $marker->ID ),
     302-                               'location'    => get_post_meta( $marker->ID, 'Location', true ),
     303-                               'venueName'   => get_post_meta( $marker->ID, 'Venue Name', true ),
     304-                               'url'         => self::get_best_wordcamp_url( $marker->ID ),
     305-                               'latitude'    => $coordinates['latitude'],
     306-                               'longitude'   => $coordinates['longitude'],
     307-                               'iconURL'     => "icon-marker-{$marker_type}-2x.png",
     308-                       );
     309-               }
     310-
     311-               $markers = apply_filters( 'wcc_get_map_markers', $markers );
     312-
     313-               set_transient( $transient_key, $markers, WEEK_IN_SECONDS );
     314-                       // todo this should probably be changed to just DAY_IN_SECONDS to avoid confusion among organizers. -- https://wordpress.slack.com/archives/meta-wordcamp/p1477323414000597
     315-                       // need to understand why it was set for so long in the first place, and test change
     316-                       // should just always display cached data and have cron job to refresh asyncronously
     317-
     318-               return $markers;
     319-       }
     320-
     321-       /**
     322-        * Filters excerpt_more.
     323-        */
     324-       static function excerpt_more( $more ) {
     325-               return '&nbsp;&hellip;';
     326-       }
     327-
     328-       /**
     329-        * Filters nav_menu_css_class.
     330-        * Make sure Schedule is current-menu-item when viewing WordCamps.
     331-        */
     332-       static function nav_menu_css_class( $classes, $item, $args ) {
     333-               if ( 'wordcamp' == get_post_type() ) {
     334-                       if ( home_url( '/schedule/' ) == trailingslashit( $item->url ) ) {
     335-                               $classes[] = 'current-menu-item';
     336-                       } else {
     337-                               $remove = array( 'current-menu-item', 'current_page_parent', 'current_page_ancestor' );
     338-                               foreach ( $remove as $class )
     339-                                       $classes = array_splice( $classes, array_search( $class, $classes ), 1 );
     340-                       }
     341-               }
     342-               return $classes;
     343-       }
     344-
     345-       public static function add_links_to_footer_menu( $items, $args ) {
     346-               if ( 'menu-footer' == $args->container_class ) {
     347-                       ob_start();
     348-
     349-                       ?>
     350-
     351-                       <li class="menu-item menu-item-type-custom menu-item-object-custom"><a href="<?php echo esc_url( get_feed_link() ); ?>">RSS (posts)</a></li>
     352-                       <li class="menu-item menu-item-type-custom menu-item-object-custom"><a href="<?php echo esc_url( get_post_type_archive_feed_link( 'wordcamp' ) ); ?>">RSS (WordCamps)</a></li>
     353-                       <li class="menu-item menu-item-type-custom menu-item-object-custom"><a href="https://wordpress.org/about/privacy/">Privacy Policy</a></li>
     354-
     355-                       <?php
     356-                       $items .= ob_get_clean();
     357-               }
     358-
     359-               return $items;
     360-       }
     361-
     362-       /**
     363-        * Get Session List
     364-        *
     365-        * Uses the WordCamp post type to loop through the latest
     366-        * WordCamps, if WordCamp URLs are valid network blogs, switches
     367-        * to blog and queries for Session.
     368-        *
     369-        * @uses switch_to_blog, get_blog_details, wp_object_cache
     370-        * @return assoc array with session and WC info
     371-        */
     372-       public static function get_sessions( $count = 4 ) {
     373-               if ( ! function_exists( 'wcpt_has_wordcamps' ) )
     374-                       return false;
     375-
     376-               // Check cache
     377-               if ( (bool) $sessions = wp_cache_get( 'wccentral_sessions_' . $count ) )
     378-                       return $sessions;
     379-
     380-               // Take latest WordCamps
     381-               $args = array(
     382-                       'posts_per_page' => $count + 10,
     383-                       'meta_key'       => 'Start Date (YYYY-mm-dd)',
     384-                       'orderby'        => 'meta_value',
     385-                       'order'          => 'ASC',
     386-                       'meta_query'     => array( array(
     387-                               'key'        => 'Start Date (YYYY-mm-dd)',
     388-                               'value'      => strtotime( '-2 days' ),
     389-                               'compare'    => '>'
     390-                       ) )
     391-               );
     392-
     393-               if ( ! wcpt_has_wordcamps( $args ) )
     394-                       return false;
     395-
     396-               // We'll hold the sessions here
     397-               $sessions = array();
     398-
     399-               // Loop through the latest WCs
     400-               while ( wcpt_wordcamps() ) {
     401-                       wcpt_the_wordcamp();
     402-
     403-                       // Store WC data (will be unavailable after switch_to_blog)
     404-                       $domain = parse_url( wcpt_get_wordcamp_url(), PHP_URL_HOST );
     405-                       $blog_details = get_blog_details( array( 'domain' => $domain ), false );
     406-
     407-                       $wordcamp_date = wcpt_get_wordcamp_start_date( 0, 'F ' );
     408-                       $wordcamp_date .= wcpt_get_wordcamp_start_date( 0, 'j' );
     409-                       if ( wcpt_get_wordcamp_end_date( 0, 'j' ) )
     410-                               $wordcamp_date .= '-' . wcpt_get_wordcamp_end_date( 0, 'j' );
     411-
     412-                       // Valid for all sessions in this WC
     413-                       $session = array(
     414-                               'wordcamp_title' => wcpt_get_wordcamp_title(),
     415-                               'wordcamp_permalink' => wcpt_get_wordcamp_permalink(),
     416-                               'wordcamp_date' => $wordcamp_date,
     417-                               'wordcamp_thumb' => get_the_post_thumbnail( get_the_ID(), 'wccentral-thumbnail-small' ),
     418-                       );
     419-
     420-                       if ( isset( $blog_details->blog_id ) && $blog_details->blog_id ) {
     421-                               $my_blog_id = (int) $blog_details->blog_id;
     422-
     423-                               switch_to_blog( $my_blog_id );
     424-
     425-                                       // Look through 5 sessions, store in $sessions array
     426-                                       $sessions_query = new WP_Query( array( 'post_type' => 'wcb_session', 'posts_per_page' => 5, 'post_status' => 'publish' ) );
     427-                                       while ( $sessions_query->have_posts() ) {
     428-                                               $sessions_query->the_post();
     429-
     430-                                               // Add the extra fields to $session and push to $sessions
     431-                                               $sessions[] = array_merge( $session, array(
     432-                                                       'name' => apply_filters( 'the_title', get_the_title() ),
     433-                                                       'speakers' => get_post_meta( get_the_ID(), '_wcb_session_speakers', true ),
     434-                                                       'permalink' => get_permalink( get_the_ID() ),
     435-                                               ) );
     436-                                       }
     437-
     438-                               restore_current_blog();
     439-                       }
     440-               }
     441-
     442-               // Randomize and pick $count
     443-               shuffle( $sessions );
     444-               $sessions = array_slice( $sessions, 0, $count );
     445-
     446-               // Cache in transients
     447-               wp_cache_set( 'wccentral_sessions_' . $count, $sessions );
     448-               return $sessions;
     449-       }
     450-
     451-       /**
     452-        * Retrieve Subscription Status from $_REQUEST
     453-        */
     454-       public static function get_subscription_status() {
     455-               return isset( $_REQUEST['subscribe'] ) ? strtolower( $_REQUEST['subscribe'] ) : false;
     456-       }
     457-
     458-       /**
     459-        * Subscription Check
     460-        * Returns true if subscriptions are available
     461-        */
     462-       public static function can_subscribe() {
     463-               return class_exists( 'Jetpack_Subscriptions' ) && is_callable( array( 'Jetpack_Subscriptions', 'subscribe' ) );
     464-       }
     465-
     466-       /**
     467-        * Fetch the latest tweets from the @WordCamp account
     468-        *
     469-        * This is an AJAX callback returning JSON-formatted data.
     470-        *
     471-        * We're manually expiring/refreshing the transient to ensure that we only ever update it when we have a
     472-        * valid response from the API. If there is a problem retrieving new data from the API, then we want to
     473-        * continue displaying the valid cached data until we can successfully retrieve new data. The data is still
     474-        * stored in a transient instead of an option, though, so that it can be cached in memory.
     475-        *
     476-        * Under certain unlikely conditions, this could cause an API rate limit violation. If the data is expired
     477-        * and we can connect to the API at the network level, but then the request fails at the application level
     478-        * (invalid credentials, etc), then we'll be hitting the API every time this function is called. If that
     479-        * does ever happen, it could be fixed by setting the timestamp of the last attempt in a transient and only
     480-        * issuing another attempt if ~2 minutes have passed.
     481-        */
     482-       public static function get_latest_tweets() {
     483-               $transient_key = 'wcc_latest_tweets';
     484-               $tweets        = get_transient( $transient_key );
     485-               $expired       = $tweets['last_update'] < strtotime( 'now - 15 minutes' );
     486-
     487-               if ( ! $tweets || $expired ) {
     488-                       $response = wp_remote_get(
     489-                               'https://api.twitter.com/1.1/statuses/user_timeline.json?count=6&trim_user=true&exclude_replies=true&include_rts=false&screen_name=wordcamp',
     490-                               array(
     491-                                       'headers' => array( 'Authorization' => 'Bearer ' . TWITTER_BEARER_TOKEN_WORDCAMP_CENTRAL ),
     492-                               )
     493-                       );
     494-
     495-                       if ( ! is_wp_error( $response ) ) {
     496-                               $tweets['tweets'] = json_decode( wp_remote_retrieve_body( $response ) );
     497-
     498-                               /*
     499-                                * Remove all but the first 3 tweets
     500-                                *
     501-                                * The Twitter API includes retweets in the `count` parameter, even if include_rts=false is passed,
     502-                                * so we have to request more tweets than we actually want and then cut it down here.
     503-                                */
     504-                               if ( is_array( $tweets['tweets'] ) ) {
     505-                                       $tweets['tweets']      = array_slice( $tweets['tweets'], 0, 3 );
     506-                                       $tweets['tweets']      = self::sanitize_format_tweets( $tweets['tweets'] );
     507-                                       $tweets['last_update'] = time();
     508-
     509-                                       set_transient( $transient_key, $tweets );
     510-                               }
     511-                       }
     512-               }
     513-
     514-               wp_send_json_success( $tweets );
     515-       }
     516-
     517-       /**
     518-        * Sanitize and format the tweet objects
     519-        *
     520-        * Whitelist the fields to cut down on how much data we're storing/transmitting, but also to force
     521-        * future devs to manually enable/sanitize any new fields that are used, which avoids the risk of
     522-        * accidentally using an unsafe value.
     523-        *
     524-        * @param array $tweets
     525-        *
     526-        * @return array
     527-        */
     528-       protected static function sanitize_format_tweets( $tweets ) {
     529-               $whitelisted_fields = array( 'id_str' => '', 'text' => '', 'created_at' => '' );
     530-
     531-               foreach ( $tweets as & $tweet ) {
     532-                       $tweet           = (object) shortcode_atts( $whitelisted_fields, $tweet );
     533-                       $tweet->id_str   = sanitize_text_field( $tweet->id_str );
     534-                       $tweet->text     = wp_kses( $tweet->text, wp_kses_allowed_html( 'data' ), array( 'http', 'https', 'mailto' ) );
     535-                       $tweet->text     = make_clickable( $tweet->text );
     536-                       $tweet->text     = self::link_hashtags_and_usernames( $tweet->text );
     537-                       $tweet->time_ago = human_time_diff( strtotime( $tweet->created_at ) );
     538-               }
     539-
     540-               return $tweets;
     541-       }
     542-
     543-       /**
     544-        * Convert usernames and hashtags to links
     545-        *
     546-        * Based on Tagregator's TGGRSourceTwitter::link_hashtags_and_usernames().
     547-        *
     548-        * @param string $text
     549-        *
     550-        * @return string
     551-        */
     552-       protected static function link_hashtags_and_usernames( $content ) {
     553-               $content = preg_replace( '/@(\w+)/',       '<a href="https://twitter.com/\\1"          class="wc-tweets-username">@\\1</a>', $content );
     554-               $content = preg_replace( '/(?<!&)#(\w+)/', '<a href="https://twitter.com/search?q=\\1" class="wc-tweets-tag"     >#\\1</a>', $content );
     555-
     556-               return $content;
     557-       }
     558-
     559-       /**
     560-        * Twenty Ten Comment
     561-        * Overrides the twentyten_comment function in the parent theme.
     562-        */
     563-       public static function twentyten_comment( $comment, $args, $depth ) {
     564-               $GLOBALS['comment'] = $comment;
     565-               switch ( $comment->comment_type ) :
     566-                       case '' :
     567-               ?>
     568-               <li <?php comment_class(); ?> id="li-comment-<?php comment_ID(); ?>">
     569-                       <div id="comment-<?php comment_ID(); ?>" class="comment-container">
     570-                       <div class="comment-author vcard">
     571-                               <?php echo get_avatar( $comment, 60 ); ?>
     572-                               <?php printf( __( '%s <span class="says">says:</span>', 'twentyten' ), sprintf( '<cite class="fn">%s</cite>', get_comment_author_link() ) ); ?>
     573-                       </div><!-- .comment-author .vcard -->
     574-                       <?php if ( $comment->comment_approved == '0' ) : ?>
     575-                               <em class="comment-awaiting-moderation"><?php _e( 'Your comment is awaiting moderation.', 'twentyten' ); ?></em>
     576-                               <br />
     577-                       <?php endif; ?>
     578-
     579-                       <div class="comment-meta commentmetadata"><a href="<?php echo esc_url( get_comment_link( $comment->comment_ID ) ); ?>">
     580-                               <?php
     581-                                       /* translators: 1: date, 2: time */
     582-                                       printf( __( '%1$s at %2$s', 'twentyten' ), get_comment_date(),  get_comment_time() ); ?></a><?php edit_comment_link( __( '(Edit)', 'twentyten' ), ' ' );
     583-                               ?>
     584-                       </div><!-- .comment-meta .commentmetadata -->
     585-
     586-                       <div class="comment-body"><?php comment_text(); ?></div>
     587-
     588-                       <div class="reply">
     589-                               <?php comment_reply_link( array_merge( $args,
     590-                                       array(
     591-                                               'depth' => $depth,
     592-                                               'max_depth' => $args['max_depth'],
     593-                                               'reply_text' => '&#10149; Reply'
     594-                                       )
     595-                               ) ); ?>
     596-                       </div><!-- .reply -->
     597-               </div><!-- #comment-##  -->
     598-
     599-               <?php
     600-                               break;
     601-                       case 'pingback'  :
     602-                       case 'trackback' :
     603-               ?>
     604-               <li class="post pingback">
     605-                       <p><?php _e( 'Pingback:', 'twentyten' ); ?> <?php comment_author_link(); ?><?php edit_comment_link( __( '(Edit)', 'twentyten' ), ' ' ); ?></p>
     606-               <?php
     607-                               break;
     608-               endswitch;
     609-       }
     610-
     611-       public static function get_upcoming_wordcamps_query( $count = 10 ) {
     612-               $query = new WP_Query( array(
     613-                       'post_type'              => WCPT_POST_TYPE_ID,
     614-                       'post_status'    => WordCamp_Loader::get_public_post_statuses(),
     615-                       'posts_per_page' => $count,
     616-                       'meta_key'       => 'Start Date (YYYY-mm-dd)',
     617-                       'orderby'        => 'meta_value',
     618-                       'order'          => 'ASC',
     619-                       'meta_query'     => array( array(
     620-                               'key'        => 'Start Date (YYYY-mm-dd)',
     621-                               'value'      => strtotime( '-2 days' ),
     622-                               'compare'    => '>'
     623-                       ) )
     624-               ) );
     625-               return $query;
     626-       }
     627-
     628-       /**
     629-        * Output the date or date range of a WordCamp event.
     630-        *
     631-        * @param int  $wordcamp_id The ID of the WordCamp post.
     632-        * @param bool $show_year   Optional. True to include the year in the date output.
     633-        */
     634-       public static function the_wordcamp_date( $wordcamp_id = 0, $show_year = false ) {
     635-               $start_day = wcpt_get_wordcamp_start_date( $wordcamp_id, 'j' );
     636-               $start_month = wcpt_get_wordcamp_start_date( $wordcamp_id, 'F' );
     637-               $end_day = wcpt_get_wordcamp_end_date( $wordcamp_id, 'j' );
     638-               $end_month = wcpt_get_wordcamp_end_date( $wordcamp_id, 'F' );
     639-
     640-               if ( $show_year ) {
     641-                       $start_year = wcpt_get_wordcamp_start_date( $wordcamp_id, 'Y' );
     642-                       $end_year   = wcpt_get_wordcamp_end_date( $wordcamp_id, 'Y' );
     643-               }
     644-
     645-               echo "$start_month $start_day";
     646-
     647-               if ( $end_day ) {
     648-                       if ( $show_year && $start_year !== $end_year ) {
     649-                               echo ", $start_year";
     650-                       }
     651-
     652-                       echo '&ndash;';
     653-
     654-                       if ( $start_month !== $end_month ) {
     655-                               echo "$end_month ";
     656-                       }
     657-
     658-                       echo $end_day;
     659-
     660-                       if ( $show_year ) {
     661-                               echo ", $end_year";
     662-                       }
     663-               } elseif ( $show_year ) {
     664-                       echo ", $start_year";
     665-               }
     666-       }
     667-
     668-       /**
     669-        * Group an array of WordCamps by year
     670-        *
     671-        * @param array $wordcamps
     672-        *
     673-        * @return array
     674-        */
     675-       public static function group_wordcamps_by_year( $wordcamps ) {
     676-               $grouped_wordcamps = array();
     677-
     678-               foreach ( $wordcamps as $wordcamp ) {
     679-                       $date = get_post_meta( $wordcamp->ID, 'Start Date (YYYY-mm-dd)', true );
     680-
     681-                       if ( $date && $year = date( 'Y', (int) $date ) ) {
     682-                               $grouped_wordcamps[ $year ][] = $wordcamp;
     683-                       }
     684-               }
     685-
     686-               return $grouped_wordcamps;
     687-       }
     688-
     689-       /**
     690-        * Returns a WordCamp's website URL if it's available, or their Central page if is isn't.
     691-        *
     692-        * @param int $post_id
     693-        *
     694-        * @return string
     695-        */
     696-       public static function get_best_wordcamp_url( $post_id = 0 ) {
     697-               $url = wcpt_get_wordcamp_url( $post_id );
     698-
     699-               if ( ! filter_var( $url, FILTER_VALIDATE_URL ) ) {
     700-                       $url = wcpt_get_wordcamp_permalink( $post_id );
     701-               }
     702-
     703-               return $url;
     704-       }
     705-
     706-       /**
     707-        * Render the [wcc_map] shortcode
     708-        *
     709-        * @param array $attributes
     710-        *
     711-        * @return string
     712-        */
     713-       public static function shortcode_map( $attributes ) {
     714-               $attributes = shortcode_atts( array( 'id' => '' ), $attributes );
     715-
     716-               ob_start();
     717-               require( __DIR__ . '/shortcode-about-map.php' );
     718-               return ob_get_clean();
     719-       }
     720-
     721-       /**
     722-        * Render the [wcc_about_stats] shortcode
     723-        *
     724-        * @param array $attributes
     725-        *
     726-        * @return string
     727-        */
     728-       public static function shortcode_about_stats( $attributes ) {
     729-               // Allow stat values to be overridden with shortcode attributes
     730-           $map_stats = shortcode_atts( self::get_map_stats(), $attributes, 'wcc_about_stats' );
     731-
     732-           // Sanitize stat values
     733-        $map_stats = array_map( 'absint', $map_stats );
     734-
     735-               ob_start();
     736-               require( __DIR__ . '/shortcode-about-stats.php' );
     737-               return ob_get_clean();
     738-       }
     739-
     740-       /**
     741-        * Gather the stats for the [wcc_about_stats] shortcode
     742-        *
     743-        * There isn't an easy way to collect the country stats programmatically, but it just takes a minute to
     744-        * manually count the number countries on the map that have pins.
     745-        *
     746-        * @return array
     747-        */
     748-       protected static function get_map_stats() {
     749-               $transient_key = 'wcc_about_map_stats';
     750-
     751-               if ( ! $map_stats = get_transient( $transient_key ) ) {
     752-                       $cities    = array();
     753-                       $wordcamps = new WP_Query( array(
     754-                               'post_type'      => 'wordcamp',
     755-                               'post_status'    => WordCamp_Loader::get_public_post_statuses(),
     756-                               'posts_per_page' => -1,
     757-                       ) );
     758-
     759-                       // Count the number of cities
     760-                       // @todo use _venue_city field since it'll be more accurate, but need to populate older camps first
     761-                       foreach ( $wordcamps->posts as $wordcamp ) {
     762-                               $url = get_post_meta( $wordcamp->ID, 'URL', true );
     763-
     764-                               if ( $hostname = parse_url( $url, PHP_URL_HOST ) ) {
     765-                                       $city = explode( '.', $hostname );
     766-                                       $cities[ $city[0] ] = true;
     767-                               }
     768-                       }
     769-
     770-                       // @todo generate countries automatically from _venue_country_code field, but need to populate older camps first
     771-
     772-                       // Compile the results
     773-                       $map_stats = array(
     774-                               'wordcamps'  => $wordcamps->found_posts,
     775-                               'cities'     => count( $cities ),
     776-                               'countries'  => 65,
     777-                               'continents' => 6,
     778-                       );
     779-
     780-                       set_transient( $transient_key, $map_stats, 2 * WEEK_IN_SECONDS );
     781-               }
     782-
     783-               return $map_stats;
     784-       }
     785-
     786-       /**
     787-        * Get T-shirt Sizes, a caching wrapper for _get_tshirt_sizes.
     788-        *
     789-        * @param int $wordcamp_id The WordCamp post ID.
     790-        *
     791-        * @return array An array of sizes.
     792-        */
     793-       public static function get_tshirt_sizes( $wordcamp_id ) {
     794-               // TODO: Implement some caching.
     795-               $sizes = self::_get_tshirt_sizes( $wordcamp_id );
     796-               return $sizes;
     797-       }
     798-
     799-       /**
     800-        * Get T-shirt Sizes.
     801-        *
     802-        * @param int $wordcamp_id The WordCamp post ID.
     803-        *
     804-        * @return array An array of sizes.
     805-        */
     806-       private static function _get_tshirt_sizes( $wordcamp_id ) {
     807-               $wordcamp = get_post( $wordcamp_id );
     808-               $sizes = array();
     809-
     810-               $wordcamp_site_id = absint( get_wordcamp_site_id( $wordcamp ) );
     811-               if ( ! $wordcamp_site_id )
     812-                       return $sizes;
     813-
     814-               wp_suspend_cache_addition( true );
     815-               switch_to_blog( $wordcamp_site_id );
     816-
     817-               $questions = get_posts( array(
     818-                       'post_type' => 'tix_question',
     819-                       'post_status' => 'publish',
     820-                       'posts_per_page' => 100,
     821-                       'fields' => 'ids',
     822-               ) );
     823-
     824-               // Aggregate only t-shirt questions.
     825-               $tshirt_questions = array();
     826-               foreach ( $questions as $question_id ) {
     827-                       if ( get_post_meta( $question_id, 'tix_type', true ) != 'tshirt' )
     828-                               continue;
     829-
     830-                       $tshirt_questions[] = $question_id;
     831-               }
     832-
     833-               $paged = 1;
     834-               while ( $attendees = get_posts( array(
     835-                       'post_type' => 'tix_attendee',
     836-                       'post_status' => array( 'publish', 'pending' ),
     837-                       'posts_per_page' => 200,
     838-                       'paged' => $paged++,
     839-                       'orderby' => 'ID',
     840-                       'order' => 'ASC',
     841-                       'fields' => 'ids',
     842-               ) ) ) {
     843-                       foreach ( $attendees as $attendee_id ) {
     844-                               $answers = get_post_meta( $attendee_id, 'tix_questions', true );
     845-                               foreach ( $answers as $question_id => $answer ) {
     846-                                       if ( in_array( $question_id, $tshirt_questions ) ) {
     847-                                               if ( ! isset( $sizes[ $answer ] ) )
     848-                                                       $sizes[ $answer ] = 0;
     849-
     850-                                               $sizes[ $answer ]++;
     851-                                       }
     852-                               }
     853-                       }
     854-               }
     855-
     856-               restore_current_blog();
     857-               wp_suspend_cache_addition( false );
     858-               arsort( $sizes );
     859-               return $sizes;
     860-       }
     861-}
     862-
     863-// Load the theme class, this is where it all starts.
     864-WordCamp_Central_Theme::on_load();
     865-
     866-// Override the parent's comment function with ours.
     867-function twentyten_comment( $comment, $args, $depth ) {
     868-       return WordCamp_Central_Theme::twentyten_comment( $comment, $args, $depth );
     869-}
     870-
     871-// This class is used to kill header images and custom background added by 2010.
     872-class WordCamp_Central_Theme_Kill_Features { function init() { return false; } }