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 ' …'; |
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' => '➥ 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 '–'; |
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; } } |
| 1 | Index: 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 ' …'; |
| 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' => '➥ 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 '–'; |
| 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; } } |