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']); }