'; print_r($array); exit; } } //function: sanitize multi-line text //used: tsml_import() and save.php if (!function_exists('sanitize_text_area')) { function sanitize_text_area($value) { return implode("\n", array_map('sanitize_text_field', explode("\n", trim($value)))); } } //function: add an admin screen update message //used: tsml_import() and admin_types.php //$type: can be success, warning or error function tsml_alert($message, $type='success') { echo '
' . $message . '
', '
', $message);
$message = str_replace('
|
' . $address . '. Response was ' . curl_error($tsml_curl_handle) . '',
);
}
//decode result
$data = json_decode($result);
if ($data->status == 'OK') {
//ok great
} elseif ($data->status == 'OVER_QUERY_LIMIT') {
//if over query limit, wait two seconds and retry, or then exit
//this isn't structured well. what if there are zero_results on the second attempt?
sleep(2);
$data = json_decode(curl_exec($ch));
if ($data->status == 'OVER_QUERY_LIMIT') {
return array(
'status' => 'error',
'reason' => 'We are over the rate limit for the Google Geocoding API.'
);
}
} elseif ($data->status == 'ZERO_RESULTS') {
if (empty($result)) {
return array(
'status' => 'error',
'reason' => 'Google could not validate the address ' . $address . '',
);
}
} else {
return array(
'status' => 'error',
'reason' => 'Google gave an unexpected response for address ' . $address . '. Response was ' . var_export($data, true) . '', ); } //check our overrides array again in case google is wrong if (array_key_exists($data->results[0]->formatted_address, $tsml_google_overrides)) { $response = $tsml_google_overrides[$data->results[0]->formatted_address]; } else { //start building response $response = array( 'formatted_address' => $data->results[0]->formatted_address, 'latitude' => $data->results[0]->geometry->location->lat, 'longitude' => $data->results[0]->geometry->location->lng, 'city' => null, ); //get city, we might need it for the region, and we are going to cache it foreach ($data->results[0]->address_components as $component) { if (in_array('locality', $component->types)) { $response['city'] = $component->short_name; } } } //cache result $addresses[$address] = $response; update_option('tsml_addresses', $addresses); //add a status and return it $response['status'] = 'geocode'; return $response; } //function: get all locations in the system //used: tsml_group_count() function tsml_get_all_groups($status='any') { return get_posts('post_type=tsml_group&post_status=' . $status . '&numberposts=-1&orderby=name&order=asc'); } //function: get all locations in the system //used: tsml_location_count(), tsml_import(), and admin_import.php function tsml_get_all_locations($status='any') { return get_posts('post_type=tsml_location&post_status=' . $status . '&numberposts=-1&orderby=name&order=asc'); } //function: get all meetings in the system //used: tsml_meeting_count(), tsml_import(), and admin_import.php function tsml_get_all_meetings($status='any') { return get_posts('post_type=tsml_meeting&post_status=' . $status . '&numberposts=-1&orderby=name&order=asc'); } //function: get all regions in the system //used: tsml_region_count(), tsml_import() and admin_import.php function tsml_get_all_regions() { return get_terms('tsml_region', array('fields'=>'ids', 'hide_empty'=>false)); } //function: get meeting ids for a data source //used: tsml_ajax_import, import/settings page function tsml_get_data_source_ids($source) { return get_posts(array( 'post_type' => 'tsml_meeting', 'numberposts' => -1, 'fields' => 'ids', 'meta_query' => array(array( 'key' => 'data_source', 'value' => $source, 'compare' => '=', )), )); } //function: get all locations with full location information //used: tsml_get_meetings() function tsml_get_groups() { $groups = array(); # Get all districts with parents, need for sub_district below $districts = $districts_with_parents = array(); $terms = get_categories(array('taxonomy' => 'tsml_district')); foreach ($terms as $term) { $districts[$term->term_id] = $term->name; if ($term->parent) $districts_with_parents[$term->term_id] = $term->parent; } # Get all locations $posts = tsml_get_all_groups('publish'); # Much faster than doing get_post_meta() over and over $group_meta = tsml_get_meta('tsml_group'); # Make an array of all groups foreach ($posts as $post) { $district_id = !empty($group_meta[$post->ID]['district_id']) ? $group_meta[$post->ID]['district_id'] : null; if (array_key_exists($district_id, $districts_with_parents)) { $district = $districts[$districts_with_parents[$district_id]]; $sub_district = $districts[$district_id]; } else { $district = !empty($districts[$district_id]) ? $districts[$district_id] : ''; $sub_district = null; } $groups[$post->ID] = array( 'group_id' => $post->ID, //so as not to conflict with another id when combined 'group' => $post->post_title, 'district' => $district, 'district_id' => $district_id, 'sub_district' => $sub_district, 'group_notes' => $post->post_content, 'website' => empty($group_meta[$post->ID]['website']) ? null : $group_meta[$post->ID]['website'], 'website_2' => empty($group_meta[$post->ID]['website_2']) ? null : $group_meta[$post->ID]['website_2'], 'email' => empty($group_meta[$post->ID]['email']) ? null : $group_meta[$post->ID]['email'], 'phone' => empty($group_meta[$post->ID]['phone']) ? null : $group_meta[$post->ID]['phone'], 'venmo' => empty($group_meta[$post->ID]['venmo']) ? null : $group_meta[$post->ID]['venmo'], 'last_contact' => empty($group_meta[$post->ID]['last_contact']) ? null : $group_meta[$post->ID]['last_contact'], ); if (current_user_can('edit_posts')) { $groups[$post->ID] = array_merge($groups[$post->ID], array( 'contact_1_name' => empty($group_meta[$post->ID]['contact_1_name']) ? null : $group_meta[$post->ID]['contact_1_name'], 'contact_1_email' => empty($group_meta[$post->ID]['contact_1_email']) ? null : $group_meta[$post->ID]['contact_1_email'], 'contact_1_phone' => empty($group_meta[$post->ID]['contact_1_phone']) ? null : $group_meta[$post->ID]['contact_1_phone'], 'contact_2_name' => empty($group_meta[$post->ID]['contact_2_name']) ? null : $group_meta[$post->ID]['contact_2_name'], 'contact_2_email' => empty($group_meta[$post->ID]['contact_2_email']) ? null : $group_meta[$post->ID]['contact_2_email'], 'contact_2_phone' => empty($group_meta[$post->ID]['contact_2_phone']) ? null : $group_meta[$post->ID]['contact_2_phone'], 'contact_3_name' => empty($group_meta[$post->ID]['contact_3_name']) ? null : $group_meta[$post->ID]['contact_3_name'], 'contact_3_email' => empty($group_meta[$post->ID]['contact_3_email']) ? null : $group_meta[$post->ID]['contact_3_email'], 'contact_3_phone' => empty($group_meta[$post->ID]['contact_3_phone']) ? null : $group_meta[$post->ID]['contact_3_phone'], )); } } return $groups; } //function: template tag to get location, attach custom fields to it //$location_id can be false if there is a global post, eg on the single-locations template page //used: single-locations.php function tsml_get_location($location_id=false) { if (!$location = get_post($location_id)) return; if ($custom = get_post_meta($location->ID)) { foreach ($custom as $key=>$value) { $location->{$key} = htmlentities($value[0], ENT_QUOTES); } } $location->post_title = htmlentities($location->post_title, ENT_QUOTES); $location->notes = esc_html($location->post_content); if ($region = get_the_terms($location, 'tsml_region')) { $location->region_id = $region[0]->term_id; $location->region = $region[0]->name; } //directions link (obsolete 4/15/2018, keeping for compatibility) $location->directions = 'https://maps.google.com/?' . http_build_query(array( 'daddr' => $location->latitude . ',' . $location->longitude, 'saddr' => 'Current Location', 'q' => $location->post_title, )); return $location; } //function: get all locations with full location information //used: tsml_import(), tsml_get_meetings(), admin_edit function tsml_get_locations() { $locations = array(); # Get all regions with parents, need for sub_region below $regions = $regions_with_parents = array(); $terms = get_categories(array('taxonomy' => 'tsml_region')); foreach ($terms as $term) { $regions[$term->term_id] = $term->name; if ($term->parent) $regions_with_parents[$term->term_id] = $term->parent; } # Get all locations $posts = tsml_get_all_locations('publish'); # Much faster than doing get_post_meta() over and over $location_meta = tsml_get_meta('tsml_location'); # Make an array of all locations foreach ($posts as $post) { $region_id = !empty($location_meta[$post->ID]['region_id']) ? $location_meta[$post->ID]['region_id'] : null; if (array_key_exists($region_id, $regions_with_parents)) { $region = $regions[$regions_with_parents[$region_id]]; $sub_region = $regions[$region_id]; } else { $region = !empty($regions[$region_id]) ? $regions[$region_id] : ''; $sub_region = null; } $locations[$post->ID] = array( 'location_id' => $post->ID, //so as not to conflict with another id when combined 'location' => $post->post_title, 'location_notes' => $post->post_content, 'location_url' => get_permalink($post->ID), 'formatted_address' => empty($location_meta[$post->ID]['formatted_address']) ? null : $location_meta[$post->ID]['formatted_address'], 'latitude' => empty($location_meta[$post->ID]['latitude']) ? null : $location_meta[$post->ID]['latitude'], 'longitude' => empty($location_meta[$post->ID]['longitude']) ? null : $location_meta[$post->ID]['longitude'], 'region_id' => $region_id, 'region' => $region, 'sub_region' => $sub_region, ); } return $locations; } //function: template tag to get meeting and location, attach custom fields to it //$meeting_id can be false if there is a global $post object, eg on the single meeting template page //used: single-meetings.php function tsml_get_meeting($meeting_id=false) { global $tsml_program, $tsml_programs; $meeting = get_post($meeting_id); $custom = get_post_meta($meeting->ID); //add optional location information if ($meeting->post_parent) { $location = get_post($meeting->post_parent); $meeting->location_id = $location->ID; $custom = array_merge($custom, get_post_meta($location->ID)); $meeting->location = htmlentities($location->post_title, ENT_QUOTES); $meeting->location_notes = esc_html($location->post_content); if ($region = get_the_terms($location, 'tsml_region')) { $meeting->region_id = $region[0]->term_id; $meeting->region = $region[0]->name; } //get other meetings at this location $meeting->location_meetings = tsml_get_meetings(array('location_id' => $location->ID)); //directions link (obsolete 4/15/2018, keeping for compatibility) $meeting->directions = 'https://maps.google.com/?' . http_build_query(array( 'daddr' => $location->latitude . ',' . $location->longitude, 'saddr' => 'Current Location', 'q' => $meeting->location, )); } //escape meeting values foreach ($custom as $key=>$value) { $meeting->{$key} = ($key == 'types') ? $value[0] : htmlentities($value[0], ENT_QUOTES); } if (empty($meeting->types)) $meeting->types = array(); if (!is_array($meeting->types)) $meeting->types = unserialize($meeting->types); $meeting->post_title = htmlentities($meeting->post_title, ENT_QUOTES); $meeting->notes = esc_html($meeting->post_content); //type description? (todo support multiple) if (!empty($tsml_programs[$tsml_program]['type_descriptions'])) { $types_with_descriptions = array_intersect($meeting->types, array_keys($tsml_programs[$tsml_program]['type_descriptions'])); foreach ($types_with_descriptions as $type) { $meeting->type_description = $tsml_programs[$tsml_program]['type_descriptions'][$type]; break; } } //if meeting is part of a group, include group info if ($meeting->group_id) { $group = get_post($meeting->group_id); $meeting->group = htmlentities($group->post_title, ENT_QUOTES); $meeting->group_notes = esc_html($group->post_content); $group_custom = get_post_meta($meeting->group_id); foreach ($group_custom as $key=>$value) { $meeting->{$key} = $value[0]; } if ($district = get_the_terms($group, 'tsml_district')) { $meeting->district_id = $district[0]->term_id; $meeting->district = $district[0]->name; } } else { $meeting->group_id = null; $meeting->group = null; } //expand and alphabetize types array_map('trim', $meeting->types); $meeting->types_expanded = array(); foreach ($meeting->types as $type) { if (!empty($tsml_programs[$tsml_program]['types'][$type])) { $meeting->types_expanded[] = $tsml_programs[$tsml_program]['types'][$type]; } } sort($meeting->types_expanded); return $meeting; } //function: get meetings based on unsanitized $arguments //$from_cache is only false when calling from tsml_cache_rebuild() //used: tsml_ajax_meetings(), single-locations.php, archive-meetings.php function tsml_get_meetings($arguments=array(), $from_cache=true) { global $tsml_cache; //start by grabbing all meetings if ($from_cache && file_exists(WP_CONTENT_DIR . $tsml_cache) && $meetings = file_get_contents(WP_CONTENT_DIR . $tsml_cache)) { $meetings = json_decode($meetings, true); } else { //from database $meetings = array(); //can specify post_status (for PR #33) if (empty($arguments['post_status'])) { $arguments['post_status'] = 'publish'; } elseif (is_array($arguments['post_status'])) { $arguments['post_status'] = array_map('sanitize_title', $arguments['post_status']); } else { $arguments['post_status'] = sanitize_title($arguments['post_status']); } $posts = get_posts(array( 'post_type' => 'tsml_meeting', 'numberposts' => -1, 'post_status' => $arguments['post_status'], )); $meeting_meta = tsml_get_meta('tsml_meeting'); $groups = tsml_get_groups(); $locations = tsml_get_locations(); $users = tsml_get_users(); //make an array of the meetings foreach ($posts as $post) { //shouldn't ever happen, but just in case if (empty($locations[$post->post_parent])) continue; //append to array $meeting = array_merge(array( 'id' => $post->ID, 'name' => $post->post_title, 'slug' => $post->post_name, 'notes' => $post->post_content, 'updated' => $post->post_modified_gmt, 'location_id' => $post->post_parent, 'url' => get_permalink($post->ID), 'day' => @$meeting_meta[$post->ID]['day'], 'time' => @$meeting_meta[$post->ID]['time'], 'end_time' => @$meeting_meta[$post->ID]['end_time'], 'time_formatted' => tsml_format_time(@$meeting_meta[$post->ID]['time']), 'email' => @$meeting_meta[$post->ID]['email'], 'website' => @$meeting_meta[$post->ID]['website'], 'website_2' => @$meeting_meta[$post->ID]['website_2'], 'phone' => @$meeting_meta[$post->ID]['phone'], 'types' => empty($meeting_meta[$post->ID]['types']) ? array() : array_values(unserialize($meeting_meta[$post->ID]['types'])), ), $locations[$post->post_parent]); //append group info to meeting if (!empty($meeting_meta[$post->ID]['group_id']) && array_key_exists($meeting_meta[$post->ID]['group_id'], $groups)) { $meeting = array_merge($meeting, $groups[$meeting_meta[$post->ID]['group_id']]); } $meetings[] = $meeting; } } //check if we are filtering $allowed = array('mode', 'day', 'time', 'region', 'district', 'type', 'query', 'group_id', 'location_id', 'latitude', 'longitude', 'distance_units', 'distance'); if ($arguments = array_intersect_key($arguments, array_flip($allowed))) { $filter = new tsml_filter_meetings($arguments); $meetings = $filter->apply($meetings); } usort($meetings, 'tsml_sort_meetings'); //dd($meetings); return $meetings; } //function: get metadata for all meetings very quickly //called in tsml_get_meetings(), tsml_get_locations() function tsml_get_meta($type, $id=null) { global $wpdb; //don't show contact information if user is not logged in //contact info still available on an individual meeting basis via tsml_get_meeting() $keys = array( 'tsml_group' => '"website", "website_2", "email", "phone", "venmo", "last_contact"' . (current_user_can('edit_posts') ? ', "contact_1_name", "contact_1_email", "contact_1_phone", "contact_2_name", "contact_2_email", "contact_2_phone", "contact_3_name", "contact_3_email", "contact_3_phone"' : ''), 'tsml_location' => '"formatted_address", "latitude", "longitude"', 'tsml_meeting' => '"day", "time", "end_time", "types", "group_id", "website", "website_2", "email", "phone", "last_contact"' . (current_user_can('edit_posts') ? ', "contact_1_name", "contact_1_email", "contact_1_phone", "contact_2_name", "contact_2_email", "contact_2_phone", "contact_3_name", "contact_3_email", "contact_3_phone"' : ''), ); if (!array_key_exists($type, $keys)) return trigger_error('tsml_get_meta for unexpected type ' . $type); $meta = array(); $query = 'SELECT post_id, meta_key, meta_value FROM ' . $wpdb->postmeta . ' WHERE meta_key IN (' . $keys[$type] . ') AND post_id ' . ($id ? '= ' . $id : 'IN (SELECT id FROM ' . $wpdb->posts . ' WHERE post_type = "' . $type . '")'); $values = $wpdb->get_results($query); foreach ($values as $value) { $meta[$value->post_id][$value->meta_key] = $value->meta_value; } //if location, get region if ($type == 'tsml_location') { $regions = $wpdb->get_results('SELECT r.`object_id` location_id, t.`term_id` region_id, t.`name` region FROM ' . $wpdb->term_relationships . ' r JOIN ' . $wpdb->term_taxonomy . ' x ON r.term_taxonomy_id = x.term_taxonomy_id JOIN ' . $wpdb->terms . ' t ON x.term_id = t.term_id WHERE x.taxonomy = "tsml_region"'); foreach ($regions as $region) { $meta[$region->location_id]['region'] = $region->region; $meta[$region->location_id]['region_id'] = $region->region_id; } } elseif ($type == 'tsml_group') { $districts = $wpdb->get_results('SELECT r.`object_id` group_id, t.`term_id` district_id, t.`name` district FROM ' . $wpdb->term_relationships . ' r JOIN ' . $wpdb->term_taxonomy . ' x ON r.term_taxonomy_id = x.term_taxonomy_id JOIN ' . $wpdb->terms . ' t ON x.term_id = t.term_id WHERE x.taxonomy = "tsml_district"'); foreach ($districts as $district) { $meta[$district->group_id]['district'] = $district->district; $meta[$district->group_id]['district_id'] = $district->district_id; } } if ($id) return $meta[$id]; return $meta; } //function get author usernames & ids //used by tsml_get_meetings() and import ajax function tsml_get_users($keyed_by_id=true) { $users = get_users(array( 'fields' => array('ID', 'user_login'), )); foreach ($users as $user) { $return[$user->ID] = $user->user_login; } return ($keyed_by_id) ? $return : array_flip($return); } //return spelled-out meeting types //called from save.php (updates) and archive-meetings.php (display) function tsml_meeting_types($types) { global $tsml_programs, $tsml_program; if (empty($tsml_programs[$tsml_program]['types'])) return; $return = array(); foreach ($types as $type) { if (array_key_exists($type, $tsml_programs[$tsml_program]['types'])) { $return[] = $tsml_programs[$tsml_program]['types'][$type]; } } sort($return); return implode(', ', $return); } //sanitize and import an array of meetings to an 'import buffer' (an wp_option that's iterated on progressively) //called from admin_import.php (both CSV and JSON) function tsml_import_buffer_set($meetings, $data_source=null) { global $tsml_programs, $tsml_program, $tsml_days; //uppercasing for value matching later $upper_types = array_map('strtoupper', $tsml_programs[$tsml_program]['types']); $upper_days = array_map('strtoupper', $tsml_days); //get users, keyed by username $users = tsml_get_users(false); $user_id = get_current_user_id(); //convert the array to UTF-8 array_walk_recursive($meetings, 'tsml_format_utf8'); //trim everything array_walk_recursive($meetings, 'tsml_import_sanitize_field'); //prepare array for import buffer $count_meetings = count($meetings); for ($i = 0; $i < $count_meetings; $i++) { $meetings[$i]['data_source'] = $data_source; //do wordpress sanitization foreach ($meetings[$i] as $key => $value) { //have to compress types down real quick (only happens with json) if (is_array($value)) $value = implode(',', $value); if (in_array($key, array('notes', 'location_notes', 'group_notes'))) { $meetings[$i][$key] = sanitize_text_area($value); } else { $meetings[$i][$key] = sanitize_text_field($value); } } //column aliases if (empty($meetings[$i]['postal_code']) && !empty($meetings[$i]['zip'])) { $meetings[$i]['postal_code'] = $meetings[$i]['zip']; } if (empty($meetings[$i]['name']) && !empty($meetings[$i]['meeting'])) { $meetings[$i]['name'] = $meetings[$i]['meeting']; } if (empty($meetings[$i]['location']) && !empty($meetings[$i]['location_name'])) { $meetings[$i]['location'] = $meetings[$i]['location_name']; } if (empty($meetings[$i]['time']) && !empty($meetings[$i]['start_time'])) { $meetings[$i]['time'] = $meetings[$i]['start_time']; } //if '@' is in address, remove it and everything after if (!empty($meetings[$i]['address']) && $pos = strpos($meetings[$i]['address'], '@')) $meetings[$i]['address'] = trim(substr($meetings[$i]['address'], 0, $pos)); //if location name is missing, use address if (empty($meetings[$i]['location'])) { $meetings[$i]['location'] = empty($meetings[$i]['address']) ? __('Meeting Location', '12-step-meeting-list') : $meetings[$i]['address']; } //day can either be 0, 1, 2, 3 or Sunday, Monday, or empty if (isset($meetings[$i]['day']) && !array_key_exists($meetings[$i]['day'], $upper_days)) { $meetings[$i]['day'] = array_search(strtoupper($meetings[$i]['day']), $upper_days); } //sanitize time & day if (empty($meetings[$i]['time']) || ($meetings[$i]['day'] === false)) { $meetings[$i]['time'] = $meetings[$i]['end_time'] = $meetings[$i]['day'] = false; //by appointment //if meeting name missing, use location if (empty($meetings[$i]['name'])) $meetings[$i]['name'] = sprintf(__('%s by Appointment', '12-step-meeting-list'), $meetings[$i]['location']); } else { //if meeting name missing, use location, day, and time if (empty($meetings[$i]['name'])) { $meetings[$i]['name'] = sprintf(__('%s %ss at %s', '12-step-meeting-list'), $meetings[$i]['location'], $tsml_days[$meetings[$i]['day']], $meetings[$i]['time']); } $meetings[$i]['time'] = tsml_format_time_reverse($meetings[$i]['time']); if (!empty($meetings[$i]['end_time'])) $meetings[$i]['end_time'] = tsml_format_time_reverse($meetings[$i]['end_time']); } //google prefers USA for geocoding if (!empty($meetings[$i]['country']) && $meetings[$i]['country'] == 'US') $meetings[$i]['country'] = 'USA'; //build address if (empty($meetings[$i]['formatted_address'])) { $address = array(); if (!empty($meetings[$i]['address'])) $address[] = $meetings[$i]['address']; if (!empty($meetings[$i]['city'])) $address[] = $meetings[$i]['city']; if (!empty($meetings[$i]['state'])) $address[] = $meetings[$i]['state']; if (!empty($meetings[$i]['postal_code'])) { if ((strlen($meetings[$i]['postal_code']) < 5) && ($meetings[$i]['country'] == 'USA')) $meetings[$i]['postal_code'] = str_pad($meetings[$i]['postal_code'], 5, '0', STR_PAD_LEFT); $address[] = $meetings[$i]['postal_code']; } if (!empty($meetings[$i]['country'])) $address[] = $meetings[$i]['country']; $meetings[$i]['formatted_address'] = implode(', ', $address); } //notes if (empty($meetings[$i]['notes'])) $meetings[$i]['notes'] = ''; if (empty($meetings[$i]['location_notes'])) $meetings[$i]['location_notes'] = ''; if (empty($meetings[$i]['group_notes'])) $meetings[$i]['group_notes'] = ''; //updated if (empty($meetings[$i]['updated']) || (!$meetings[$i]['updated'] = strtotime($meetings[$i]['updated']))) $meetings[$i]['updated'] = time(); $meetings[$i]['post_modified'] = date('Y-m-d H:i:s', $meetings[$i]['updated']); $meetings[$i]['post_modified_gmt'] = get_gmt_from_date($meetings[$i]['post_modified']); //author if (!empty($meetings[$i]['author']) && array_key_exists($meetings[$i]['author'], $users)) { $meetings[$i]['post_author'] = $users[$meetings[$i]['author']]; } else { $meetings[$i]['post_author'] = $user_id; } //default region to city if not specified if (empty($meetings[$i]['region']) && !empty($meetings[$i]['city'])) $meetings[$i]['region'] = $meetings[$i]['city']; //sanitize types (they can be Closed or C) if (empty($meetings[$i]['types'])) $meetings[$i]['types'] = ''; $types = explode(',', $meetings[$i]['types']); $meetings[$i]['types'] = $unused_types = array(); foreach ($types as $type) { $upper_type = trim(strtoupper($type)); if (array_key_exists($upper_type, $upper_types)) { $meetings[$i]['types'][] = $upper_type; } elseif (in_array($upper_type, array_values($upper_types))) { $meetings[$i]['types'][] = array_search($upper_type, $upper_types); } else { $unused_types[] = $type; } } //if a meeting is both open and closed, make it closed if (in_array('C', $meetings[$i]['types']) && in_array('O', $meetings[$i]['types'])) { $meetings[$i]['types'] = array_diff($meetings[$i]['types'], array('O')); } //append unused types to notes if (count($unused_types)) { if (!empty($meetings[$i]['notes'])) $meetings[$i]['notes'] .= str_repeat(PHP_EOL, 2); $meetings[$i]['notes'] .= implode(', ', $unused_types); } //make sure we're not double-listing types $meetings[$i]['types'] = array_unique($meetings[$i]['types']); //clean up foreach(array('address', 'city', 'state', 'postal_code', 'country', 'updated') as $key) { if (isset($meetings[$i][$key])) unset($meetings[$i][$key]); } //preserve row number for errors later $meetings[$i]['row'] = $i + 2; } //allow user-defined function to filter the meetings (for gal-aa.org) if (function_exists('tsml_import_filter')) { $meetings = array_filter($meetings, 'tsml_import_filter'); } //dd($meetings); //prepare import buffer in wp_options update_option('tsml_import_buffer', $meetings, false); } //function: filter workaround for setting post_modified dates //used: tsml_ajax_import() function tsml_import_post_modified($data, $postarr) { if (!empty($postarr['post_modified'])) { $data['post_modified'] = $postarr['post_modified']; } if (!empty($postarr['post_modified_gmt'])) { $data['post_modified_gmt'] = $postarr['post_modified_gmt']; } return $data; } //function: handle FNV (GSO) imports //used: tsml_import() - put here for code isolation function tsml_import_reformat_fnv($rows) { $meetings = array(); $header = array_shift($rows); //check if it's a FNV file $required_fnv_columns = array('ServiceNumber', 'GroupName', 'CountryCode', 'City', 'District', 'Website', 'DateChanged', 'PrimaryFirstName', 'SecondaryPrimaryEmail', 'Meeting1Addr1', 'Meeting1SUNTimes'); $missing_fnv_columns = array_diff($required_fnv_columns, $header); //dd($missing_fnv_columns); if (!empty($missing_fnv_columns)) { array_unshift($rows, $header); return $rows; } $short_days = array('SUN', 'MON', 'TUE', 'WED', 'THU', 'FRI', 'SAT'); $days = array('Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'); $all_types = array(); foreach ($rows as $row) { $row = array_combine($header, $row); if ($row['Type'] !== 'Regular') continue; for ($number = 1; $number < 5; $number++) { foreach ($short_days as $index => $day) { $key = 'Meeting' . $number . $day . 'Times'; if (!empty($row[$key])) { $times = explode(' ', strtolower($row[$key])); foreach ($times as $time) { $time_parts = explode('(', str_replace(')', '', $time)); $time = array_shift($time_parts); $types = array(); foreach ($time_parts as $part) { if (($part == 'c') || strstr($part, 'closed')) { $types[] = 'Closed'; } elseif (($part == 'o') || strstr($part, 'open')) { $types[] = 'Open'; } if (strstr($part, 'women') || strstr($part, 'lady')) { $types[] = 'Women'; } elseif (strstr($part, 'men')) { $types[] = 'Men'; } if (strstr($part, 'bb') || strstr($part, 'big book')) $types[] = 'Big Book'; if (strstr($part, '12') || strstr($part, 'step')) $types[] = 'Step Study'; if (strstr($part, 'candlelight')) $types[] = 'Candlelight'; if (strstr($part, 'speaker')) $types[] = 'Speaker'; if (strstr($part, 'disc')) $types[] = 'Discussion'; if (strstr($part, 'newcomer') || strstr($part, 'new comer')) $types[] = 'Newcomer'; if (strstr($part, 'grapevine')) $types[] = 'Grapevine'; if (strstr($part, 'spanish')) $types[] = 'Spanish'; } $all_types = array_merge($all_types, $types); //add language as type $types[] = $row['Language']; if ($pos = strpos($row['Meeting' . $number . 'Addr1'], '(')) { $row['Meeting' . $number . 'Addr1'] = substr($row['Meeting' . $number . 'Addr1'], 0, $pos); $row['Meeting' . $number . 'Comments'] .= PHP_EOL . substr($row['Meeting' . $number . 'Addr1'], $pos); } $meetings[] = array( 'Name' => $row['GroupName'], 'Day' => $days[$index], 'Time' => $time, 'Location' => $row['Meeting' . $number . 'Location'], 'Address' => $row['Meeting' . $number . 'Addr1'], 'City' => $row['Meeting' . $number . 'City'], 'State' => $row['Meeting' . $number . 'StateCode'], 'Postal Code' => $row['Meeting' . $number . 'Zip'], 'Country' => $row['CountryCode'], 'Types' => implode(', ', $types), 'Region' => $row['City'], 'Group' => $row['GroupName'] . ' #' . $row['ServiceNumber'], 'Website' => $row['Website'], 'Updated' => $row['DateChanged'], 'District' => $row['District'] ? sprintf(__('District %s', '12-step-meeting-list'), $row['District']) : null, 'Last Contact' => $row['DateChanged'], 'Notes' => $row['Meeting' . $number . 'Comments'], 'Contact 1 Name' => $row['PrimaryFirstName'] . ' ' . $row['PrimaryLastName'], 'Contact 1 Phone' => preg_replace('~\D~', '', $row['PrimaryPrimaryPhone']), 'Contact 1 Email' => substr($row['PrimaryPrimaryEmail'], strpos($row['PrimaryPrimaryEmail'], ' ') + 1), 'Contact 2 Name' => $row['SecondaryFirstName'] . ' ' . $row['SecondaryLastName'], 'Contact 2 Phone' => preg_replace('~\D~', '', $row['SecondaryPrimaryPhone']), 'Contact 2 Email' => substr($row['SecondaryPrimaryEmail'], strpos($row['SecondaryPrimaryEmail'], ' ') + 1), ); } } } } } //debugging types $all_types = array_unique($all_types); sort($all_types); $return = array(array_keys($meetings[0])); foreach ($meetings as $meeting) { $return[] = array_values($meeting); } return $return; } //function: turn "string" into string //used: tsml_ajax_import() inside array_map function tsml_import_sanitize_field($value) { //preserve
' . $line . '
'; } } return $paragraphs; } //function: set an option with the currently-used types //used: tsml_import() and save.php function tsml_update_types_in_use() { global $tsml_types_in_use, $wpdb; //shortcut to getting all meta values without getting all posts first $types = $wpdb->get_col('SELECT m.meta_value FROM ' . $wpdb->postmeta . ' m JOIN ' . $wpdb->posts . ' p ON m.post_id = p.id WHERE p.post_type = "tsml_meeting" AND m.meta_key = "types" AND p.post_status = "publish"'); //master array $all_types = array(); //loop through results and append to master array foreach ($types as $type) { $type = unserialize($type); if (is_array($type)) $all_types = array_merge($all_types, $type); } //update global variable $tsml_types_in_use = array_unique($all_types); //set option value update_option('tsml_types_in_use', $tsml_types_in_use); } //function: helper to debug out of memory errors //used: ad-hoc function tsml_report_memory() { $size = memory_get_peak_usage(true); $units = array('B', 'KB', 'MB', 'GB'); die(round($size / pow(1024, ($i = floor(log($size, 1024)))), 2) . $units[$i]); } //function: sanitize a time field //used: save.php function tsml_sanitize_time($string) { $string = sanitize_text_field($string); if ($time = strtotime($string)) return date('H:i', $time); return null; } //function: sort an array of meetings //used: as a callback in tsml_get_meetings() //method: sort by // 1) day, following "week starts on" user preference, with appointment meetings last, // 2) followed by time, where the day starts at 5am, // 3) followed by location name, // 4) followed by meeting name function tsml_sort_meetings($a, $b) { global $tsml_days_order, $tsml_sort_by; //sub_regions are regions in this scenario if (!empty($a['sub_region'])) $a['region'] = $a['sub_region']; if (!empty($b['sub_region'])) $b['region'] = $b['sub_region']; //custom sort order? if ($tsml_sort_by !== 'time') { if ($a[$tsml_sort_by] != $b[$tsml_sort_by]) { return strcmp($a[$tsml_sort_by], $b[$tsml_sort_by]); } } //get the user-settable order of days $a_day_index = isset($a['day']) && strlen($a['day']) ? array_search($a['day'], $tsml_days_order) : false; $b_day_index = isset($b['day']) && strlen($b['day']) ? array_search($b['day'], $tsml_days_order) : false; if ($a_day_index === false && $b_day_index !== false) { return 1; } elseif ($a_day_index !== false && $b_day_index === false) { return -1; } elseif ($a_day_index != $b_day_index) { return $a_day_index - $b_day_index; } else { //days are the same or both null $a_time = empty($a['time']) ? '' : (($a['time'] == '00:00') ? '23:59' : $a['time']); $b_time = empty($b['time']) ? '' : (($b['time'] == '00:00') ? '23:59' : $b['time']); $time_diff = strcmp($a_time, $b_time); if ($time_diff) { return $time_diff; } else { $a_location = empty($a['location']) ? '' : $a['location']; $b_location = empty($b['location']) ? '' : $b['location']; $location_diff = strcmp($a_location, $b_location); if ($location_diff) { return $location_diff; } else { $a_name = empty($a['name']) ? '' : $a['name']; $b_name = empty($b['name']) ? '' : $b['name']; return strcmp($a_name, $b_name); } } } } //function: tokenize string for the typeaheads //used: ajax functions function tsml_string_tokens($string) { //shorten words that have quotes in them instead of splitting them $string = html_entity_decode($string); $string = str_replace("'", '', $string); $string = str_replace('’', '', $string); //remove everything that's not a letter or a number $string = preg_replace("/[^a-zA-Z 0-9]+/", ' ', $string); //return array return array_values(array_unique(array_filter(explode(' ', $string)))); }