Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 44 additions & 6 deletions api.wordpress.org/public_html/events/1.0/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ function build_response( $location, $location_args ) {
*/
function is_client_core( $user_agent ) {
// This doesn't simply return the value of `strpos()` because `0` means `true` in this context
if ( false === strpos( $user_agent, 'WordPress/' ) ) {
if ( false === strpos( $user_agent, 'WordPress/' ) && false === strpos( $user_agent, 'WordPress.com/' ) ) {
return false;
}

Expand Down Expand Up @@ -908,7 +908,7 @@ function get_events( $args = array() ) {

$raw_events = $wpdb->get_results( $wpdb->prepare(
"SELECT
`type`, `title`, `url`,
`type`, `source_id`, `title`, `url`,
`meetup`, `meetup_url`,
`date_utc`, `date_utc_offset`, `end_date`,
`location`, `country`, `latitude`, `longitude`
Expand All @@ -933,7 +933,7 @@ function get_events( $args = array() ) {
$events[] = array(
'type' => $event->type,
'title' => $event->title,
'url' => $event->url,
'url' => add_click_tracking( $event->url, $event ),
'meetup' => $event->meetup,
'meetup_url' => $event->meetup_url,

Expand Down Expand Up @@ -1326,6 +1326,10 @@ function maybe_add_regional_wordcamps( $local_events, $region_data, $user_agent,
// before the event until it's over).
}

foreach ( $regional_wordcamps as &$event ) {
$event['url'] = add_click_tracking( $event['url'], $event );
}

return array_merge( $regional_wordcamps, $local_events );
}

Expand Down Expand Up @@ -1452,7 +1456,7 @@ function pin_next_online_wordcamp( $events, $user_agent, $current_time, $user_co
if ( false === $next_online_camp ) {
$raw_camp = $wpdb->get_row( "
SELECT
`title`, `url`, `meetup`, `meetup_url`, `date_utc`, `date_utc_offset`, `end_date`, `country`, `latitude`, `longitude`
`type`, `source_id`, `title`, `url`, `meetup`, `meetup_url`, `date_utc`, `date_utc_offset`, `end_date`, `country`, `latitude`, `longitude`
FROM `wporg_events`
WHERE
type = 'wordcamp' AND
Expand All @@ -1465,7 +1469,8 @@ function pin_next_online_wordcamp( $events, $user_agent, $current_time, $user_co

if ( isset( $raw_camp->url ) ) {
$next_online_camp = array(
'type' => 'wordcamp',
'type' => $raw_camp->type,
'source_id' => $raw_camp->source_id,
'title' => $raw_camp->title,
'url' => $raw_camp->url,
'meetup' => $raw_camp->meetup,
Expand Down Expand Up @@ -1522,6 +1527,9 @@ function pin_next_online_wordcamp( $events, $user_agent, $current_time, $user_co
* potentially-interesting event, and crowding out local events.
*/
if ( $camp_is_in_users_country || $camp_is_in_next_two_weeks ) {

$next_online_camp['url'] = add_click_tracking( $next_online_camp['url'], $next_online_camp );

array_unshift( $events, $next_online_camp );
}
}
Expand Down Expand Up @@ -1565,7 +1573,7 @@ function pin_next_workshop_discussion_group( $events, $user_agent ) {
$next_discussion_group = array(
'type' => 'meetup',
'title' => $raw_discussion_group->title,
'url' => $raw_discussion_group->url,
'url' => add_click_tracking( $raw_discussion_group->url, $raw_discussion_group ),
'meetup' => $raw_discussion_group->meetup,
'meetup_url' => $raw_discussion_group->meetup_url,

Expand Down Expand Up @@ -1628,6 +1636,7 @@ function pin_one_off_events( $events, $current_time ) {
);

if ( $current_time > strtotime( 'December 11, 2024' ) && $current_time < strtotime( 'December 17, 2024' ) ) {
$sotw['url'] = add_click_tracking( $sotw['url'], $sotw );
array_unshift( $events, $sotw );
}

Expand Down Expand Up @@ -1724,4 +1733,33 @@ function get_bounded_coordinates( $lat, $lon, $distance_in_km = 50 ) {
);
}

/**
* Add click tracking through a redirect.
*
* @param string $url The original URL.
* @param object $event The event object.
* @return string The tracked URL.
*/
function add_click_tracking( $url, $event ) {
// Inconsistent in API, sometimes arrays.
$event = (object) $event;

// Start down under.
if ( empty( $event->country ) || 'AU' !== strtoupper( $event->country ) ) {
return $url;
}

// Need both type and source_id to build the tracked link.
if ( empty( $event->type ) || empty( $event->source_id ) ) {
return $url;
}

$tracked_link = 'https://api.wordpress.org/events/redirect/';
$tracked_link .= '?' . urlencode( $event->type ) . '=' . urlencode( $event->source_id );
$tracked_link .= '&url=' . urlencode( $url );
$tracked_link .= '&source=' . ( is_client_core( $_SERVER['HTTP_USER_AGENT'] ) ? 'core' : 'api' );

return $tracked_link;
}

main();
83 changes: 83 additions & 0 deletions api.wordpress.org/public_html/events/redirect/index.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
<?php
/**
* Redirect endpoint for event tracking.
*
* This file is intended to be a simple endpoint that can generate some basic
* stats on our events API.
*/
$base_dir = dirname( dirname( __DIR__ ) );
require_once $base_dir . '/init.php';
require_once $base_dir . '/includes/hyperdb/bb-10-hyper-db.php';

// for bump_stats_extra().
include_once WPORGPATH . 'wp-content/mu-plugins/1-stats-extra.php';

// Cache in browsers for a day, but not server.
header( 'Cache-Control: private, max-age=86400' );
allow_cors_requests();

$event = false;
$url = urldecode( $_REQUEST['url'] ); // Fallback incase of unexpected DB failure.
$type = '';
$source_id = '';
foreach ( [ 'meetup', 'wordcamp' ] as $field ) {
if ( isset( $_REQUEST[ $field ] ) ) {
$type = $field;
$source_id = (int) $_REQUEST[ $field ];
break;
}
}

// Fetch the event, redirect to the stored URL.
if ( $type && $source_id ) {
$event = $wpdb->get_row(
$wpdb->prepare(
"SELECT url, country
FROM wporg_events
WHERE type = %s AND source_id = %d
LIMIT 1",
$type,
$source_id
)
);
}

if ( ! empty( $event->url ) ) {
$url = $event->url; // We trust the URL we've stored.
} else {
// If no event, validate the provided $url and redirect there.
$type = 'unknown-' . $type;
// Only allow redirects to known domains.
if ( ! preg_match( '#^https?://([^/]+\.)?(meetup.com|wordpress.org|wordcamp.org|doaction.org)/#i', $url ) ) {
$url = 'https://events.wordpress.org/';
}

// We could just sign the request, but for simplicity, we'll just use the above validation.
}

// Redirect
header( 'Location: ' . $url, true, 302 );

if ( function_exists( 'fastcgi_finish_request' ) ) {
fastcgi_finish_request();
}

if ( function_exists( 'bump_stats_extra' ) ) {
bump_stats_extra( 'events-clicks', $type );
if ( $event ) {
bump_stats_extra( 'events-clicks-country', strtoupper( $event->country ) );
}
if ( isset( $_GET['ref'] ) && in_array( $_GET['ref'], [ 'core', 'api', 'events', 'email' ], true ) ) {
bump_stats_extra( 'events-clicks-ref', $_GET['ref'] );
}
}

if ( $type && $source_id && $event ) {
$wpdb->query(
$wpdb->prepare(
"UPDATE wporg_events SET clicks = clicks + 1 WHERE type = %s AND source_id = %d",
$type,
$source_id
)
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,6 @@ protected function get_meetup_client() {
* be careful to maintain consistency when making any changes to this.
*/
public function prime_events_cache() {
global $wpdb;

$this->log( 'started call #' . did_action( 'owpe_prime_events_cache' ) );

if ( did_action( 'owpe_prime_events_cache' ) > 1 ) {
Expand All @@ -116,7 +114,6 @@ public function prime_events_cache() {

foreach ( $events as $event ) {
$row_values = array(
'id' => null,
'type' => $event->type,
'source_id' => $event->source_id,
'status' => $event->status,
Expand All @@ -133,27 +130,68 @@ public function prime_events_cache() {
'country' => $event->country_code,
'latitude' => $event->latitude,
'longitude' => $event->longitude,
'clicks' => 0,
'created_at' => gmdate( 'Y-m-d H:i:s' ),
);

// Latitude and longitude are required by the database, so skip events that don't have one.
if ( empty( $row_values['latitude'] ) || empty( $row_values['longitude'] ) ) {
continue;
}

/*
* Insert the events into the table, without creating duplicates
*
* Note: Since replace() is matching against a unique key rather than the primary `id` key, it's
* expected for each row to be deleted and re-inserted, making the IDs increment each time.
*
* See http://stackoverflow.com/a/12205366/450127
*/
$wpdb->replace( self::EVENTS_TABLE, $row_values );
$keys_not_to_update = array(
'clicks',
'created_at',
);

$this->insert_on_duplicate_key_update(
$wpdb->prefix . self::EVENTS_TABLE,
$row_values,
array_diff( array_keys( $row_values ), $keys_not_to_update )
);
}

$this->log( "finished job\n\n" );
}

/**
* INSERT INTO ... ON DUPLICATE KEY UPDATE ... helper
*
* @param string $table
* @param array $data
* @param array $update_keys
*/
protected function insert_on_duplicate_key_update( $table, $data, $update_keys ) {
global $wpdb;

$sql = 'INSERT INTO %i (';
$args = array( $table );
$fields_sql = '';
$values_sql = '';
$values_args = [];
foreach ( $data as $field => $value ) {
$fields_sql .= '%i, ';
$values_sql .= '%s, ';

$args[] = $field;
$values_args[] = $value;
}
$sql .= rtrim( $fields_sql, ', ' ) . ') VALUES ( ' . rtrim( $values_sql, ', ' ) . ') ';

$args = array_merge( $args, $values_args );
unset( $values_args );

$sql .= 'ON DUPLICATE KEY UPDATE ';
foreach ( $update_keys as $field ) {
$sql .= '%i = VALUES(%i), ';
$args[] = $field;
$args[] = $field;
}
$sql = rtrim( $sql, ', ' );

$wpdb->query( $wpdb->prepare( $sql, ...$args ) );
}

/**
* Enqueue scripts and styles
*/
Expand Down