html_entity_decode($location['location']),
'formatted_address' => $location['formatted_address'],
'latitude' => $location['latitude'],
'longitude' => $location['longitude'],
'region' => $location['region_id'],
'timezone' => $location['timezone'],
'notes' => html_entity_decode($location['location_notes']),
'tokens' => tsml_string_tokens($location['location']),
];
}
wp_send_json($results);
});
// ajax for the meeting edit group typeahead
add_action('wp_ajax_tsml_groups', function () {
global $tsml_contact_fields;
tsml_require_meetings_permission();
$groups = get_posts(['post_type' => 'tsml_group', 'numberposts' => -1]);
$results = [];
foreach ($groups as $group) {
$group_custom = get_post_meta($group->ID);
// basic group info
$result = [
'value' => $group->post_title,
'notes' => $group->post_content,
'tokens' => tsml_string_tokens($group->post_title),
];
foreach ($tsml_contact_fields as $field => $type) {
$result[$field] = !empty($group_custom[$field][0]) ? $group_custom[$field][0] : null;
}
// district
if ($district = get_the_terms($group, 'tsml_district')) {
$result += [
'district' => $district[0]->term_id,
];
}
$results[] = $result;
}
wp_send_json($results);
});
// ajax for the search typeahead on the public meeting directory
add_action('wp_ajax_tsml_typeahead', 'tsml_ajax_typeahead');
add_action('wp_ajax_nopriv_tsml_typeahead', 'tsml_ajax_typeahead');
function tsml_ajax_typeahead()
{
// regions
// phpcs:ignore
$regions = get_terms('tsml_region');
$results = [];
foreach ($regions as $region) {
$results[] = [
'value' => html_entity_decode($region->name),
'type' => 'region',
'tokens' => tsml_string_tokens($region->name),
'id' => $region->slug // needed by legacy search typeahead menu
];
}
// locations
$locations = get_posts(['post_type' => 'tsml_location', 'numberposts' => -1]);
foreach ($locations as $location) {
$results[] = [
'value' => html_entity_decode($location->post_title),
'type' => 'location',
'tokens' => tsml_string_tokens($location->post_title),
'url' => get_permalink($location->ID),
];
}
// groups
$groups = get_posts(['post_type' => 'tsml_group', 'numberposts' => -1]);
foreach ($groups as $group) {
$results[] = [
'value' => html_entity_decode($group->post_title),
'type' => 'group',
'tokens' => tsml_string_tokens($group->post_title),
];
}
wp_send_json($results);
}
// ajax for address checking
add_action('wp_ajax_tsml_address', function () {
tsml_require_meetings_permission();
if (
!$posts = get_posts([
'post_type' => 'tsml_location',
'numberposts' => 1,
'meta_key' => 'formatted_address',
'meta_value' => sanitize_text_field($_GET['formatted_address']),
])
) {
wp_send_json(false);
}
$region = get_the_terms($posts[0]->ID, 'tsml_region');
// return info to user
wp_send_json([
'location' => $posts[0]->post_title,
'location_notes' => $posts[0]->post_content,
'region' => $region[0]->term_id,
]);
});
// get all contact email addresses (for europe)
// linked from admin_import.php
add_action('wp_ajax_contacts', function () {
global $wpdb, $tsml_nonce;
tsml_require_meetings_permission();
if (!isset($_GET['nonce']) || !wp_verify_nonce($_GET['nonce'], $tsml_nonce)) {
wp_die(__('Security check failed.', '12-step-meeting-list'), 403);
}
$post_ids = $wpdb->get_col('SELECT id FROM ' . $wpdb->posts . ' WHERE post_type IN ("tsml_group", "tsml_meeting") AND post_status = "publish"');
if (empty($post_ids)) {
die('');
}
$emails = $wpdb->get_col('SELECT meta_value FROM ' . $wpdb->postmeta . ' WHERE meta_key IN ("email", "contact_1_email", "contact_2_email", "contact_3_email") AND post_id IN (' . implode(',', array_map('intval', $post_ids)) . ')');
$emails = array_unique(array_filter($emails));
sort($emails);
die(wp_kses_post(implode(',
', $emails)));
});
// function: export csv
// used: linked from admin-import.php
add_action('wp_ajax_csv', function () {
// going to need this later
global $tsml_days, $tsml_programs, $tsml_program, $tsml_sharing, $tsml_export_columns, $tsml_custom_meeting_fields;
// security
tsml_require_meetings_permission();
// get data source
$meetings = tsml_get_meetings([], false, true);
// helper vars
$delimiter = ',';
$escape = '"';
// allow user-defined fields to be exported
if (!empty($tsml_custom_meeting_fields)) {
$tsml_export_columns = array_merge($tsml_export_columns, $tsml_custom_meeting_fields);
}
// do header
$return = implode($delimiter, array_values($tsml_export_columns)) . PHP_EOL;
// get the preferred time format setting
$time_format = get_option('time_format');
// append meetings
foreach ($meetings as $meeting) {
$line = [];
foreach ($tsml_export_columns as $column => $value) {
if (in_array($column, ['time', 'end_time'])) {
$line[] = empty($meeting[$column]) ? null : date($time_format, strtotime($meeting[$column]));
} elseif ($column == 'day') {
$line[] = $tsml_days[$meeting[$column]];
} elseif ($column == 'types') {
$types = !empty($meeting[$column]) ? $meeting[$column] : [];
if (!is_array($types)) {
$types = [];
}
foreach ($types as &$type) {
$type = $tsml_programs[$tsml_program]['types'][trim($type)];
}
sort($types);
$line[] = $escape . implode(', ', $types) . $escape;
} elseif (strstr($column, 'notes')) {
$line[] = $escape . strip_tags(str_replace($escape, str_repeat($escape, 2), !empty($meeting[$column]) ? $meeting[$column] : '')) . $escape;
} elseif (array_key_exists($column, $meeting)) {
$line[] = $escape . str_replace($escape, '', $meeting[$column]) . $escape;
} else {
$line[] = '';
}
}
$return .= implode($delimiter, $line) . PHP_EOL;
}
// headers to trigger file download
header('Content-Type: text/csv');
header('Content-Disposition: attachment; filename="meetings.csv"');
// output
wp_die(wp_kses($return, []));
});
// function: receives user feedback, sends email to admin
// used: single-meetings.php
add_action('wp_ajax_tsml_feedback', 'tsml_ajax_feedback');
add_action('wp_ajax_nopriv_tsml_feedback', 'tsml_ajax_feedback');
function tsml_ajax_feedback()
{
global $tsml_feedback_addresses, $tsml_nonce;
$meeting = tsml_get_meeting(intval($_POST['meeting_id']));
$name = sanitize_text_field($_POST['tsml_name']);
$email = sanitize_email($_POST['tsml_email']);
$message = '
' . nl2br(tsml_sanitize_text_area(stripslashes($_POST['tsml_message']))) . '
'; $message_lines = [ __('Requested By', '12-step-meeting-list') => $name . ' <' . $email . '>', __('Meeting', '12-step-meeting-list') => '' . $meeting->post_title . '', __('When', '12-step-meeting-list') => tsml_format_day_and_time($meeting->day, $meeting->time), ]; if (!empty($meeting->types)) { $message_lines[__('Types', '12-step-meeting-list')] = implode(', ', $meeting->types); } if (!empty($meeting->notes)) { $message_lines[__('Notes', '12-step-meeting-list')] = $meeting->notes; } if (!empty($meeting->location)) { $message_lines[__('Location', '12-step-meeting-list')] = $meeting->location; } if (!empty($meeting->formatted_address)) { $message_lines[__('Address', '12-step-meeting-list')] = $meeting->formatted_address; } if (!empty($meeting->region)) { $message_lines[__('Region', '12-step-meeting-list')] = $meeting->region; } if (!empty($meeting->location_notes)) { $message_lines[__('Location Notes', '12-step-meeting-list')] = $meeting->location_notes; } foreach ($message_lines as $key => $value) { $message .= '' . $key . ': ' . $value . '
'; } // if meeting was imported and has feedback_emails set, email them instead $to_email_addresses = $tsml_feedback_addresses; if (!empty($meeting->data_source) && !empty($meeting->feedback_emails)) { $to_email_addresses = $meeting->feedback_emails; } // email vars if (!isset($_POST['tsml_nonce']) || !wp_verify_nonce($_POST['tsml_nonce'], $tsml_nonce)) { esc_html_e('Error: nonce value not set correctly. Email was not sent.', '12-step-meeting-list'); } elseif (empty($to_email_addresses) || empty($name) || !is_email($email) || empty($message)) { esc_html_e('Error: required form value missing. Email was not sent.', '12-step-meeting-list'); } else { // send HTML email $subject = __('Meeting Feedback Form', '12-step-meeting-list') . ': ' . $meeting->post_title; if (tsml_email($to_email_addresses, $subject, $message, $name . ' <' . $email . '>')) { esc_html_e('Thank you for your feedback.', '12-step-meeting-list'); } else { global $phpmailer; if (!empty($phpmailer->ErrorInfo)) { // translators: %s is the error message echo esc_html(sprintf(__('Error: %s', '12-step-meeting-list'), $phpmailer->ErrorInfo)); } else { esc_html_e('An error occurred while sending email!', '12-step-meeting-list'); } } } exit; } // function: get geocode for string // used: public meeting directory, admin_meeting.php add_action('wp_ajax_tsml_geocode', 'tsml_ajax_geocode'); add_action('wp_ajax_nopriv_tsml_geocode', 'tsml_ajax_geocode'); function tsml_ajax_geocode() { global $tsml_nonce; if (!wp_verify_nonce(@$_GET['nonce'], $tsml_nonce)) { tsml_ajax_unauthorized(); } wp_send_json(tsml_geocode(@$_GET['address'])); } // function: get a list of all the geocodes in the database // used: for debugging add_action('wp_ajax_tsml_geocodes', function () { global $tsml_google_overrides; tsml_require_meetings_permission(); $addresses = tsml_get_option_array('tsml_addresses'); // handle get request to remove an address from the cache if (isset($_GET['remove'])) { $remove = stripslashes($_GET['remove']); if (!empty($addresses[$remove])) { unset($addresses[$remove]); update_option('tsml_addresses', $addresses); } } // include the google overrides if (!empty($tsml_google_overrides)) { $addresses = array_merge($addresses, $tsml_google_overrides); } // add useful links foreach ($addresses as $address => $geocode) { $addresses[$address]['map_address'] = 'https://www.google.com/maps/search/?api=1&query=' . urlencode($geocode['formatted_address']); $addresses[$address]['map_coordinates'] = 'https://www.google.com/maps/search/?api=1&query=' . urlencode($geocode['latitude'] . ',' . $geocode['longitude']); if ($geocode['status'] === 'geocode') { $addresses[$address]['remove'] = admin_url('admin-ajax.php?action=tsml_geocodes&remove=' . urlencode($address)); } } ksort($addresses); wp_send_json($addresses); }); // ajax function to import the meetings in the import buffer // used by admin_import.php add_action('wp_ajax_tsml_import', function () { tsml_require_meetings_permission(); $response = tsml_import_buffer_next(25); wp_send_json($response); }); // api ajax function // used by theme, web app, mobile app add_action('wp_ajax_meetings', 'tsml_ajax_meetings'); add_action('wp_ajax_nopriv_meetings', 'tsml_ajax_meetings'); function tsml_ajax_meetings() { global $tsml_sharing, $tsml_sharing_keys, $tsml_nonce; // accepts GET or POST $input = empty($_POST) ? $_GET : $_POST; if ($tsml_sharing == 'open') { // sharing is open } elseif (!empty($input['nonce']) && wp_verify_nonce($input['nonce'], $tsml_nonce)) { // nonce checks out } elseif (!empty($input['key']) && array_key_exists($input['key'], $tsml_sharing_keys)) { // key checks out } else { tsml_ajax_unauthorized(); } // ignore post_status from user input to prevent exposing drafts unset($input['post_status']); if (!headers_sent()) { header('Access-Control-Allow-Origin: *'); } wp_send_json(tsml_get_meetings($input)); } // create and email a sharing key to meeting guide add_action('wp_ajax_meeting_guide', 'tsml_ajax_meeting_guide'); add_action('wp_ajax_nopriv_meeting_guide', 'tsml_ajax_meeting_guide'); function tsml_ajax_meeting_guide() { global $tsml_sharing_keys; $mg_key = false; // check for existing keys foreach ($tsml_sharing_keys as $key => $value) { if ($value == 'Meeting Guide') { $mg_key = $key; } } // add new key if (empty($mg_key)) { $mg_key = md5(uniqid('Meeting Guide', true)); $tsml_sharing_keys[$mg_key] = 'Meeting Guide'; asort($tsml_sharing_keys); update_option('tsml_sharing_keys', $tsml_sharing_keys); } // build url $message = admin_url('admin-ajax.php?') . http_build_query( array( 'action' => 'meetings', 'key' => $mg_key, ) ); // send email if (tsml_email(TSML_MEETING_GUIDE_APP_NOTIFY, 'Sharing Key', $message)) { die('sent'); } die('not sent!'); } // send a 401 and exit function tsml_ajax_unauthorized() { if (!headers_sent()) { header('HTTP/1.1 401 Unauthorized', true, 401); } wp_send_json(['error' => 'HTTP/1.1 401 Unauthorized']); }