<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[492] sites/trunk/wordpress.org/public_html/wp-content/plugins: Add WordPress Events plugin from @iandunn.</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://meta.trac.wordpress.org/changeset/492">492</a></dd>
<dt>Author</dt> <dd>Otto42</dd>
<dt>Date</dt> <dd>2014-04-01 21:39:48 +0000 (Tue, 01 Apr 2014)</dd>
</dl>

<h3>Log Message</h3>
<pre>Add WordPress Events plugin from @iandunn. See <a href="http://meta.trac.wordpress.org/ticket/407">#407</a></pre>

<h3>Added Paths</h3>
<ul>
<li>sites/trunk/wordpress.org/public_html/wp-content/plugins/official-wordpress-events/</li>
<li><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginsofficialwordpresseventsofficialwordpresseventphp">sites/trunk/wordpress.org/public_html/wp-content/plugins/official-wordpress-events/official-wordpress-event.php</a></li>
<li><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginsofficialwordpresseventsofficialwordpresseventsphp">sites/trunk/wordpress.org/public_html/wp-content/plugins/official-wordpress-events/official-wordpress-events.php</a></li>
<li><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginsofficialwordpresseventstemplateeventsphp">sites/trunk/wordpress.org/public_html/wp-content/plugins/official-wordpress-events/template-events.php</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="sitestrunkwordpressorgpublic_htmlwpcontentpluginsofficialwordpresseventsofficialwordpresseventphp"></a>
<div class="addfile"><h4>Added: sites/trunk/wordpress.org/public_html/wp-content/plugins/official-wordpress-events/official-wordpress-event.php (0 => 492)</h4>
<pre class="diff"><span>
<span class="info">--- sites/trunk/wordpress.org/public_html/wp-content/plugins/official-wordpress-events/official-wordpress-event.php                          (rev 0)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/official-wordpress-events/official-wordpress-event.php     2014-04-01 21:39:48 UTC (rev 492)
</span><span class="lines">@@ -0,0 +1,26 @@
</span><ins>+<?php
+
+/*
+ * An official WordPress event
+ * 
+ * This doesn't have any real functionality, but it exists to provide a standard data structure
+ * for events across various event types.
+ */
+class Official_WordPress_Event {
+       public $type, $title, $url, $start_timestamp, $end_timestamp, $location;
+
+       /**
+        * Constructor
+        * 
+        * @param array $members
+        */
+       public function __construct( $members ) {
+               $valid_members = get_object_vars( $this );
+
+               foreach ( $members as $member => $value ) {
+                       if ( array_key_exists( $member, $valid_members ) ) {
+                               $this->$member = $value;
+                       }
+               }
+       }
+}
</ins></span></pre></div>
<a id="sitestrunkwordpressorgpublic_htmlwpcontentpluginsofficialwordpresseventsofficialwordpresseventsphp"></a>
<div class="addfile"><h4>Added: sites/trunk/wordpress.org/public_html/wp-content/plugins/official-wordpress-events/official-wordpress-events.php (0 => 492)</h4>
<pre class="diff"><span>
<span class="info">--- sites/trunk/wordpress.org/public_html/wp-content/plugins/official-wordpress-events/official-wordpress-events.php                         (rev 0)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/official-wordpress-events/official-wordpress-events.php    2014-04-01 21:39:48 UTC (rev 492)
</span><span class="lines">@@ -0,0 +1,248 @@
</span><ins>+<?php
+/*
+Plugin Name: Official WordPress Events
+Description: Retrieves data on official WordPress events
+Version:     0.1
+Author:      WordPress.org Meta Team
+*/
+
+class Official_WordPress_Events {
+       const WORDCAMP_API_BASE_URL = 'https://central.wordcamp.org/wp-json.php';
+       const MEETUP_API_BASE_URL   = 'https://api.meetup.com/';
+       const MEETUP_MEMBER_ID      = 72560962;
+       const POSTS_PER_PAGE        = 50;
+
+
+       /*
+        * @todo
+        *
+        * Setup `owe_error_email_addresses` callback and MEETUP_API_KEY, etc when deploy to production
+        * Ability to feature a camp in a hero area
+        * Add a "load more" button that retrieves more events via AJAX and updates the DOM. Have each click load the next month of events?
+        */
+
+
+       /**
+        * Constructor
+        */
+       public function __construct() {
+               add_shortcode( 'official_wordpress_events', array( $this, 'render_events' ) );
+       }
+
+       /**
+        * Gather the events data and render the events template with it
+        */
+       public function render_events() {
+               $events = $this->get_all_events();
+               
+               if ( $events ) {
+                       require_once( __DIR__ . '/template-events.php' );
+               }
+       }
+
+       /**
+        * Get all official events
+        * 
+        * @return array
+        */
+       protected function get_all_events() {
+               $events = array_merge( $this->get_wordcamp_events(), $this->get_meetup_events() );
+               usort( $events, array( $this, 'sort_events' ) );
+               
+               return $events;
+       }
+
+       /**
+        * Sort events based on start timestamp 
+        * 
+        * This is a callback for usort()
+        * 
+        * @param $a
+        * @param $b
+        * @return int
+        */
+       protected function sort_events( $a, $b ) {
+               if ( $a->start_timestamp == $b->start_timestamp ) {
+                       return 0;
+               } else {
+                       return $a->start_timestamp > $b->start_timestamp ? 1 : -1;
+               }
+       }
+
+       /**
+        * Retrieve events fromm the WordCamp.org API
+        *
+        * @return array
+        */
+       protected function get_wordcamp_events() {
+               $events    = array();
+               $response  = $this->remote_get( self::WORDCAMP_API_BASE_URL . '/posts/?type=wordcamp' );
+               $wordcamps = json_decode( wp_remote_retrieve_body( $response ) );
+               
+               if ( $wordcamps ) {
+                       foreach ( $wordcamps as $wordcamp ) {
+                               if ( isset( $wordcamp->post_meta->{'Start Date (YYYY-mm-dd)'}[0] ) && $wordcamp->post_meta->{'Start Date (YYYY-mm-dd)'}[0] < time() ) {
+                                       continue;
+                                       
+                                       // todo if https://github.com/WP-API/WP-API/pull/118 is merged, then finish register_json_query_vars() in WordCamp_Loader and filter this via the url
+                                       // restrict it to just the upcoming month?
+                               }
+                                       
+                               $events[] = new Official_WordPress_Event( array(
+                                       'type'            => 'wordcamp',
+                                       'title'           => $wordcamp->title,
+                                       'url'             => $wordcamp->post_meta->URL[0],
+                                       'start_timestamp' => $wordcamp->post_meta->{'Start Date (YYYY-mm-dd)'}[0],
+                                       'end_timestamp'   => $wordcamp->post_meta->{'End Date (YYYY-mm-dd)'}[0],
+                                       'localtion'       => $wordcamp->post_meta->Location[0]
+                               ) );
+                       }
+               }
+               
+               return $events;
+       }
+
+       /**
+        * Get WordPress meetups from the Meetup.com API
+        *
+        * @return array
+        */
+       protected function get_meetup_events() {
+               $events = array();
+
+               if ( ! defined( 'MEETUP_API_KEY' ) || ! MEETUP_API_KEY || ! $groups = $this->get_meetup_group_ids() ) {
+                       return $events;
+               }
+               
+               $response = $this->remote_get( sprintf(
+                       '%s2/events?group_id=%s&time=0,1m&page=%d&key=%s',
+                       self::MEETUP_API_BASE_URL,
+                       implode( ',', $groups ),
+                       self::POSTS_PER_PAGE,
+                       MEETUP_API_KEY
+               ) );
+
+               $meetups = json_decode( wp_remote_retrieve_body( $response ) );
+               if ( ! empty ( $meetups->results ) ) {
+                       $meetups = $meetups->results;
+               } else {
+                       $meetups = array();
+               }
+               
+               if ( $meetups ) {
+                       foreach ( $meetups as $meetup ) {
+                               $location        = array();
+                               $start_timestamp = ( $meetup->time / 1000 ) + ( $meetup->utc_offset / 1000 );    // convert to seconds
+                               
+                               foreach ( array( 'city', 'state', 'country' ) as $part ) {
+                                       if ( ! empty( $meetup->venue->$part ) ) {
+                                               if ( in_array( $part, array( 'state', 'country' ) ) ) {
+                                                       $location[] = strtoupper( $meetup->venue->$part );
+                                               } else {
+                                                       $location[] = $meetup->venue->$part;
+                                               }
+                                       }
+                               }
+                               
+                               $events[] = new Official_WordPress_Event( array(
+                                       'type'            => 'meetup',
+                                       'title'           => $meetup->name,
+                                       'url'             => $meetup->event_url,
+                                       'start_timestamp' => $start_timestamp,
+                                       'end_timestamp'   => ( empty ( $meetup->duration ) ? $start_timestamp : $start_timestamp + ( $meetup->duration / 1000 ) ),     // convert to seconds
+                                       'location'        => empty( $location ) ? '' : implode( ' ', $location )
+                               ) );
+                       }
+               }
+
+               return $events;
+       }
+       
+       /*
+        * Gets the IDs of all of the meetup groups associated
+        * 
+        * @return array
+        */
+       protected function get_meetup_group_ids() {
+               if ( ! defined( 'MEETUP_API_KEY' ) || ! MEETUP_API_KEY ) {
+                       return array();
+               }
+               
+               $response = $this->remote_get( sprintf(
+                       '%s2/profiles?&member_id=%d&key=%s',
+                       self::MEETUP_API_BASE_URL,
+                       self::MEETUP_MEMBER_ID,
+                       MEETUP_API_KEY
+               ) );
+
+               $group_ids = json_decode( wp_remote_retrieve_body( $response ) );
+       
+               if ( ! empty ( $group_ids->results ) ) {
+                       $group_ids = wp_list_pluck( $group_ids->results, 'group' );
+                       $group_ids = wp_list_pluck( $group_ids, 'id' );
+               }
+               
+               if ( ! isset( $group_ids ) || ! is_array( $group_ids ) ) {
+                       $group_ids = array();
+               }
+               
+               return $group_ids;
+       }
+
+       /**
+        * Wrapper for wp_remote_get()
+        *
+        * This adds caching and error logging/notification
+        *
+        * @param string $url
+        * @param array  $args
+        * @return false|array|WP_Error False if a valid $url was not passed; otherwise the results from wp_remote_get()
+        */
+       protected function remote_get( $url, $args = array() ) {
+               $response = $error = false;
+               
+               if ( $url ) {
+                       $transient_key = 'owe_' . wp_hash( $url . print_r( $args, true ) );
+                       
+                       if ( ! $response = get_transient( $transient_key ) ) {
+                               $response = wp_remote_get( $url, $args );
+       
+                               if ( is_wp_error( $response ) ) {
+                                       $error = sprintf(
+                                               'Recieved WP_Error message: %s; Request was to %s; Arguments were: %s',
+                                               implode( ', ', $response->get_error_messages() ),
+                                               $url,
+                                               print_r( $args, true )
+                                       );
+                               } elseif ( 200 != $response['response']['code'] ) {
+                                       // trigger_error() has a message limit of 1024 bytes, so we truncate $response['body'] to make sure that $body doesn't get truncated.
+       
+                                       $error = sprintf(
+                                               'Recieved HTTP code: %s and body: %s. Request was to: %s; Arguments were: %s',
+                                               $response['response']['code'],
+                                               substr( sanitize_text_field( $response['body'] ), 0, 500 ),
+                                               $url,
+                                               print_r( $args, true )
+                                       );
+                                       
+                                       $response = new WP_Error( 'woe_invalid_http_response', 'Invalid HTTP response code', $response ); 
+                               }
+       
+                               if ( $error ) {
+                                       trigger_error( sprintf( '%s error for %s: %s', __METHOD__, parse_url( site_url(), PHP_URL_HOST ), sanitize_text_field( $error ) ), E_USER_WARNING );
+       
+                                       if ( $to = apply_filters( 'owe_error_email_addresses', array() ) ) {
+                                               wp_mail( $to, sprintf( '%s error for %s', __METHOD__, parse_url( site_url(), PHP_URL_HOST ) ), sanitize_text_field( $error ) );
+                                       }
+                               } else {
+                                       set_transient( $transient_key, $response, HOUR_IN_SECONDS );
+                               }
+                       }
+               }
+
+               return $response;
+       }
+}
+
+require_once( __DIR__ . DIRECTORY_SEPARATOR . 'official-wordpress-event.php' );
+$GLOBALS['Official_WordPress_Events'] = new Official_WordPress_Events();
</ins></span></pre></div>
<a id="sitestrunkwordpressorgpublic_htmlwpcontentpluginsofficialwordpresseventstemplateeventsphp"></a>
<div class="addfile"><h4>Added: sites/trunk/wordpress.org/public_html/wp-content/plugins/official-wordpress-events/template-events.php (0 => 492)</h4>
<pre class="diff"><span>
<span class="info">--- sites/trunk/wordpress.org/public_html/wp-content/plugins/official-wordpress-events/template-events.php                           (rev 0)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/official-wordpress-events/template-events.php      2014-04-01 21:39:48 UTC (rev 492)
</span><span class="lines">@@ -0,0 +1,17 @@
</span><ins>+<div id="ofe_events">
+       <ul>
+               <?php foreach ( $events as $event ) : ?>
+
+                       <li>
+                               <a href="<?php echo esc_attr( esc_url( $event->url ) ); ?>">
+                                       <?php echo esc_html( $event->title ); ?>
+                               </a><br />
+
+                               <?php echo esc_html( date( 'l, F jS | g:i a', (int) $event->start_timestamp ) ); ?><br />
+
+                               <?php echo esc_html( $event->location ); ?>
+                       </li>
+
+               <?php endforeach; ?>
+       </ul>
+</div> <!-- end #ofe_events -->
</ins></span></pre>
</div>
</div>

</body>
</html>