// JS wp_enqueue_script('twmp-script', plugins_url('dummy.js', __FILE__), array('jquery'), null, true); $ajax_url = admin_url('admin-ajax.php'); $js = " (function($){ function twmpOpenModal(memberId) { if (!memberId) return; $.ajax({ url: '{$ajax_url}', method: 'POST', dataType: 'json', data: { action: 'twmp_get_member_profile', member_id: memberId }, success: function(res) { console.log(\"AJAX RESPONSE:\", res); if (!res || !res.success || !res.data) return; var d = res.data; var backdrop = $('.twmp-modal-backdrop').first(); if (!backdrop.length) return; backdrop.find('.twmp-modal-photo').attr('src', d.photo || ''); backdrop.find('.twmp-modal-name').text(d.name || ''); backdrop.find('.twmp-modal-memberid').text(d.memberID ? 'Member ID: ' + d.memberID : ''); var infoHtml = ''; function row(label, value) { if (!value) return ''; return '
' + label + ': ' + value + '
'; } infoHtml += row('Address', d.address); infoHtml += row('Birth Date', d.birthDate); infoHtml += row('Spouse', d.spouse); infoHtml += row('Phone', d.phone); infoHtml += row('Email', d.email); infoHtml += row('GHIN', d.ghin); backdrop.find('.twmp-info').html(infoHtml); var champHtml = ''; if (d.championships && d.championships.length) { champHtml += '
Championships
'; for (var i = 0; i < d.championships.length; i++) { champHtml += '
' + d.championships[i] + '
'; } } backdrop.find('.twmp-championships').html(champHtml); var posHtml = ''; if (d.positions && d.positions.length) { posHtml += '
Committees
'; for (var j = 0; j < d.positions.length; j++) { posHtml += '
' + d.positions[j] + '
'; } } backdrop.find('.twmp-positions').html(posHtml); // TW Officers var twOffHtml = ''; if (d.tw_officers && d.tw_officers.length) { twOffHtml += '
TW Officers
'; d.tw_officers.forEach(function(item) { twOffHtml += '
' + item.title + ' — ' + item.years + '
'; }); } backdrop.find('.twmp-tw-officers').html(twOffHtml); // TW Board var twBoardHtml = ''; if (d.tw_board && d.tw_board.length) { twBoardHtml += '
TW Board of Governors
'; d.tw_board.forEach(function(item) { twBoardHtml += '
' + item.label + ' — ' + item.years + '
'; }); } backdrop.find('.twmp-tw-board').html(twBoardHtml); // TWSF Officers var twsfOffHtml = ''; if (d.twsf_officers && d.twsf_officers.length) { twsfOffHtml += '
TWSF Officers
'; d.twsf_officers.forEach(function(item) { twsfOffHtml += '
' + item.title + ' — ' + item.years + '
'; }); } backdrop.find('.twmp-twsf-officers').html(twsfOffHtml); // TWSF Board var twsfBoardHtml = ''; if (d.twsf_board && d.twsf_board.length) { twsfBoardHtml += '
TWSF Board of Trustees
'; d.twsf_board.forEach(function(item) { twsfBoardHtml += '
' + item.label + ' — ' + item.years + '
'; }); } backdrop.find('.twmp-twsf-board').html(twsfBoardHtml); var extraHtml = ''; if (d.bio) { extraHtml += '
Bio
'; extraHtml += '
' + d.bio + '
'; } if (d.wikilink || d.gravelink) { extraHtml += '
More About This Member
'; extraHtml += '
'; if (d.wikilink) { extraHtml += 'Wikipedia Profile'; } if (d.gravelink) { extraHtml += 'Grave Site Record'; } extraHtml += '
'; } backdrop.find('.twmp-extra').html(extraHtml); backdrop.css('display', 'flex'); } }); } // Global click handler: any image with data-member-id $(document).on('click', 'img[data-member-id]', function(e){ e.preventDefault(); var memberId = $(this).data('member-id'); twmpOpenModal(memberId); }); // Close modal $(document).on('click', '.twmp-modal-close, .twmp-modal-backdrop', function(e){ if ($(e.target).closest('.twmp-modal').length && !$(e.target).hasClass('twmp-modal-close')) { return; } $('.twmp-modal-backdrop').hide(); }); })(jQuery); "; wp_add_inline_script('twmp-script', $js); } add_action('wp_enqueue_scripts', 'twmp_enqueue_assets'); /** * ============================================================ * SECTION 2 — Modal HTML injected into footer * ============================================================ */ function twmp_modal_footer() { if (is_admin()) { return; } ?>
×
Personal Info
add_action('wp_footer', 'twmp_modal_footer'); /** * ================================================================ * SECTION 3 — Output buffering: auto-tag member images site-wide * ================================================================ */ function twmp_start_buffer() { if (is_admin()) { return; } ob_start('twmp_buffer_callback'); } add_action('wp', 'twmp_start_buffer'); function twmp_buffer_callback($html) { // Quick check to avoid unnecessary work if (stripos($html, '/members/') === false) { return $html; } // Capture: // 1 = attributes before src // 2 = full src value // 3 = member ID (YYYY-NN) // 4 = attributes after src $pattern = '/]*)src=["\']([^"\']*\/members\/([0-9]{4}-[0-9]+)\.(?:jpg|jpeg|png|gif))["\']([^>]*)>/i'; $callback = function($matches) { $before = $matches[1]; // attributes before src $src = $matches[2]; // full src $id = $matches[3]; // member ID (YYYY-NN) $after = $matches[4]; // attributes after src // If already tagged, return original if (stripos($matches[0], 'data-member-id=') !== false) { return $matches[0]; } // Build corrected tag return ''; }; $new_html = preg_replace_callback($pattern, $callback, $html); return $new_html ?: $html; } /** * ============================================================ * SECTION 4 — Helper: Build address string from wp_tw_address * ============================================================ */ function twmp_build_address($address_row) { if (!$address_row) { return ''; } $parts = array(); if (!empty($address_row->line1)) { $parts[] = $address_row->line1; } if (!empty($address_row->line2)) { $parts[] = $address_row->line2; } $city_line = ''; if (!empty($address_row->city)) { $city_line .= $address_row->city; } if (!empty($address_row->state)) { $city_line .= ($city_line ? ', ' : '') . $address_row->state; } if (!empty($address_row->postal)) { $city_line .= ($city_line ? ' ' : '') . $address_row->postal; } if ($city_line) { $parts[] = $city_line; } return implode('
', array_map('esc_html', $parts)); } /** * ============================================================ * SECTION 5 — Helper: Get championships for a member * ============================================================ * * Uses: * wp_tw_tournamentResult (memberID int, tournamentID, year, position) * wp_tw_tournament (id, name, ...) */ function twmp_get_championships($member_int_id) { global $wpdb; $tr_table = $wpdb->prefix . 'tw_tournamentResult'; $t_table = $wpdb->prefix . 'tw_tournament'; $sql = $wpdb->prepare(" SELECT tr.year, t.name FROM {$tr_table} tr JOIN {$t_table} t ON tr.tournamentID = t.id WHERE tr.memberID = %d AND tr.position = 1 ORDER BY tr.year DESC, t.name ASC ", $member_int_id); $rows = $wpdb->get_results($sql); $out = array(); if ($rows) { foreach ($rows as $r) { $year = intval($r->year); $name = esc_html($r->name); $out[] = "{$year} – {$name}"; } } return $out; } /** * ============================================================ * SECTION 6 — Helper: Get committees for a member * ============================================================ * * Uses: * wp_tw_committeeMembers / wp_twsf_committeeMembers * wp_tw_committeeRoles / wp_twsf_committeeRoles * wp_tw_committee / wp_twsf_committee */ function twmp_get_positions($member_int_id) { global $wpdb; // Debug flag (set globally if you prefer) if (!defined('TWMP_DEBUG')) { define('TWMP_DEBUG', false); } // TW tables $cm_tw = $wpdb->prefix . 'tw_committeeMembers'; $cr_tw = $wpdb->prefix . 'tw_committeeRoles'; $c_tw = $wpdb->prefix . 'tw_committee'; // TWSF tables $cm_sf = $wpdb->prefix . 'twsf_committeeMembers'; $cr_sf = $wpdb->prefix . 'twsf_committeeRoles'; $c_sf = $wpdb->prefix . 'twsf_committee'; // ✅ fixed name // Combined query (no parentheses around SELECT blocks) $sql = $wpdb->prepare(" SELECT cm.year AS year, c.name AS committeeName, cr.role AS role FROM {$cm_tw} cm JOIN {$cr_tw} cr ON cm.committeeRoleID = cr.id JOIN {$c_tw} c ON cr.committeeID = c.id WHERE cm.memberID = %d UNION ALL SELECT cm.year AS year, c.name AS committeeName, cr.role AS role FROM {$cm_sf} cm JOIN {$cr_sf} cr ON cm.committeeRoleID = cr.id JOIN {$c_sf} c ON cr.committeeID = c.id WHERE cm.memberID = %d ORDER BY year DESC, committeeName ASC ", $member_int_id, $member_int_id); // Debug: log SQL if (TWMP_DEBUG) { error_log("TWMP DEBUG — Committee SQL:\n" . $sql); } $rows = $wpdb->get_results($sql); // Debug: log SQL errors if (TWMP_DEBUG && $wpdb->last_error) { error_log("TWMP SQL ERROR: " . $wpdb->last_error); } // If query failed or returned nothing, surface that in debug mode if (TWMP_DEBUG && empty($rows)) { return array( "DEBUG: No committee rows returned", $sql, $wpdb->last_error ); } $out = array(); if ($rows) { foreach ($rows as $r) { $year = intval($r->year); $committee = esc_html($r->committeeName); $role = esc_html($r->role); $out[] = "{$year} - {$committee} {$role}"; } } return $out; } /* ============================================================ OFFICERS & BOARDS — YEAR COLLAPSING + DATA BUILDER ============================================================ */ function twmp_format_years($years) { if (empty($years)) return ''; sort($years); $ranges = []; $start = $years[0]; $prev = $years[0]; for ($i = 1; $i < count($years); $i++) { if ($years[$i] == $prev + 1) { $prev = $years[$i]; } else { $ranges[] = ($start == $prev) ? "$start" : "$start–$prev"; $start = $years[$i]; $prev = $years[$i]; } } $ranges[] = ($start == $prev) ? "$start" : "$start–$prev"; return implode(', ', $ranges); } /** * Get TW + TWSF Officers & Boards for a member (grouped by organization). * * @param int $member_int_id Internal numeric member ID (wp_tw_member.id) * @return array * * Structure: * [ * 'tw_officers' => [ * ['title' => 'President', 'years' => '2021–2023'], * ... * ], * 'tw_board' => [ * ['label' => 'TW Board of Governors', 'years' => '2022–2024'] * ], * 'twsf_officers' => [ * ['title' => 'Chairman', 'years' => '2021–2023'], * ... * ], * 'twsf_board' => [ * ['label' => 'TWSF Board of Trustees', 'years' => '2021–2023'] * ], * ] */ function twmp_get_officers_and_boards( $member_int_id ) { global $wpdb; error_log("OFFICERS: function entered with ID " . $member_int_id); $member_int_id = (int) $member_int_id; $tw_position_table = $wpdb->prefix . 'tw_position'; $tw_position_member_table = $wpdb->prefix . 'tw_positionMember'; $tw_board_table = $wpdb->prefix . 'tw_board'; $twsf_position_table = $wpdb->prefix . 'twsf_position'; $twsf_position_member_table = $wpdb->prefix . 'twsf_positionMember'; $twsf_board_table = $wpdb->prefix . 'twsf_board'; // Helper: collapse an array of years into ranges like "2021–2023, 2018". $build_year_ranges = function( $years ) { if ( empty( $years ) ) { return ''; } $years = array_map( 'intval', $years ); $years = array_unique( $years ); sort( $years ); // ascending $ranges = []; $start = $years[0]; $prev = $years[0]; for ( $i = 1; $i < count( $years ); $i++ ) { $y = $years[ $i ]; if ( $y === $prev + 1 ) { // still in a consecutive run $prev = $y; continue; } // close current range if ( $start === $prev ) { $ranges[] = (string) $start; } else { $ranges[] = $start . '–' . $prev; } // start new range $start = $y; $prev = $y; } // close last range if ( $start === $prev ) { $ranges[] = (string) $start; } else { $ranges[] = $start . '–' . $prev; } return implode( ', ', $ranges ); }; $result = [ 'tw_officers' => [], 'tw_board' => [], 'twsf_officers' => [], 'twsf_board' => [], ]; // -------------------------------------------------- // TW OFFICERS // -------------------------------------------------- $tw_officer_rows = $wpdb->get_results( $wpdb->prepare( " SELECT p.name AS position_name, pm.year FROM {$tw_position_member_table} pm INNER JOIN {$tw_position_table} p ON pm.positionID = p.id WHERE pm.memberID = %d ORDER BY pm.year ASC ", $member_int_id ), ARRAY_A ); if ( ! empty( $tw_officer_rows ) ) { $by_position = []; foreach ( $tw_officer_rows as $row ) { $pos_name = trim( $row['position_name'] ); $year = (int) $row['year']; if ( ! isset( $by_position[ $pos_name ] ) ) { $by_position[ $pos_name ] = []; } $by_position[ $pos_name ][] = $year; } foreach ( $by_position as $pos_name => $years ) { $result['tw_officers'][] = [ 'title' => $pos_name, 'years' => $build_year_ranges( $years ), ]; } // Sort TW officers by title for consistency usort( $result['tw_officers'], function( $a, $b ) { return strcasecmp( $a['title'], $b['title'] ); } ); } error_log("OFFICERS: TW officers built"); // -------------------------------------------------- // TW BOARD OF GOVERNORS // -------------------------------------------------- $tw_board_rows = $wpdb->get_results( $wpdb->prepare( " SELECT yearElected FROM {$tw_board_table} WHERE memberID = %d ORDER BY yearElected ASC ", $member_int_id ), ARRAY_A ); if ( ! empty( $tw_board_rows ) ) { $years = array_map( function( $row ) { return (int) $row['yearElected']; }, $tw_board_rows ); $result['tw_board'][] = [ 'label' => 'TW Board of Governors', 'years' => $build_year_ranges( $years ), ]; } error_log("OFFICERS: TW board built"); // -------------------------------------------------- // TWSF OFFICERS // -------------------------------------------------- $twsf_officer_rows = $wpdb->get_results( $wpdb->prepare( " SELECT p.name AS position_name, pm.year FROM {$twsf_position_member_table} pm INNER JOIN {$twsf_position_table} p ON pm.positionID = p.id WHERE pm.memberID = %d ORDER BY pm.year ASC ", $member_int_id ), ARRAY_A ); if ( ! empty( $twsf_officer_rows ) ) { $by_position = []; foreach ( $twsf_officer_rows as $row ) { $pos_name = trim( $row['position_name'] ); $year = (int) $row['year']; if ( ! isset( $by_position[ $pos_name ] ) ) { $by_position[ $pos_name ] = []; } $by_position[ $pos_name ][] = $year; } foreach ( $by_position as $pos_name => $years ) { $result['twsf_officers'][] = [ 'title' => $pos_name, 'years' => $build_year_ranges( $years ), ]; } // Sort TWSF officers by title usort( $result['twsf_officers'], function( $a, $b ) { return strcasecmp( $a['title'], $b['title'] ); } ); } error_log("OFFICERS: TWSF officers built"); // -------------------------------------------------- // TWSF BOARD OF TRUSTEES // -------------------------------------------------- $twsf_board_rows = $wpdb->get_results( $wpdb->prepare( " SELECT yearElected FROM {$twsf_board_table} WHERE memberID = %d ORDER BY yearElected ASC ", $member_int_id ), ARRAY_A ); if ( ! empty( $twsf_board_rows ) ) { $years = array_map( function( $row ) { return (int) $row['yearElected']; }, $twsf_board_rows ); $result['twsf_board'][] = [ 'label' => 'TWSF Board of Trustees', 'years' => $build_year_ranges( $years ), ]; } return $result; } error_log("OFFICERS: TWSF board built"); /** * ============================================================ * SECTION 7 — AJAX: Fetch member profile data * ============================================================ * * Input: * member_id (string like 1904-01 from filename and wp_tw_member.memberID) */ function twmp_get_member_profile_ajax() { if (empty($_POST['member_id'])) { wp_send_json(array('success' => false)); } $member_key = sanitize_text_field($_POST['member_id']); global $wpdb; $member_table = $wpdb->prefix . 'tw_member'; $image_table = $wpdb->prefix . 'tw_image'; $address_table = $wpdb->prefix . 'tw_address'; // Get member row by memberID (string like 1904-01) $member = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$member_table} WHERE memberID = %s", $member_key ) ); if (!$member) { wp_send_json(array('success' => false)); } // Photo $photo_url = ''; if (!empty($member->imageID)) { $image = $wpdb->get_row( $wpdb->prepare( "SELECT filename FROM {$image_table} WHERE id = %d", $member->imageID ) ); if ($image && !empty($image->filename)) { $photo_url = $image->filename; } } // Address $address_str = ''; if (!empty($member->addressID)) { $address = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$address_table} WHERE id = %d", $member->addressID ) ); $address_str = twmp_build_address($address); } // Spouse full name $spouse_full = ''; if (!empty($member->spouse)) { $spouse_full = $member->spouse; if (!empty($member->spouseLastName)) { $spouse_full .= ' ' . $member->spouseLastName; } } // Championships & Positions use internal int id $member_int_id = (int) $member->id; $championships = twmp_get_championships($member_int_id); $positions = twmp_get_positions($member_int_id); error_log("STEP: About to build final data array"); $data = array( 'name' => $member->name, 'memberID' => $member->memberID, 'photo' => $photo_url, 'address' => $address_str, 'birthDate' => $member->birthDate, 'spouse' => $spouse_full, 'phone' => $member->phone, 'email' => $member->email, 'pccNumber' => $member->PCCNumber, 'ghin' => $member->GHIN, 'bio' => $member->bio, 'wikilink' => $member->wikilink, 'gravelink' => $member->gravelink, 'championships' => $championships, 'positions' => $positions, ); error_log("STEP: Final data array built"); // ✅ FIXED — use internal numeric ID $officers = twmp_get_officers_and_boards($member_int_id); $data['tw_officers'] = $officers['tw_officers']; $data['tw_board'] = $officers['tw_board']; $data['twsf_officers'] = $officers['twsf_officers']; $data['twsf_board'] = $officers['twsf_board']; error_log("STEP: Sending JSON"); error_log("JSON OUT: " . print_r($data, true)); wp_send_json(array( 'success' => true, 'data' => $data, )); } add_action('wp_ajax_twmp_get_member_profile', 'twmp_get_member_profile_ajax'); add_action('wp_ajax_nopriv_twmp_get_member_profile', 'twmp_get_member_profile_ajax'); /** * ============================================================ * SECTION 8 — OPTIONAL: [tw_member_profile_gallery] shortcode * ============================================================ * * Not required for site-wide behavior; just for gallery pages. */ function twmp_member_profile_gallery_shortcode($atts) { global $wpdb; $atts = shortcode_atts(array( 'count' => 0, ), $atts, 'tw_member_profile_gallery'); $member_table = $wpdb->prefix . 'tw_member'; $image_table = $wpdb->prefix . 'tw_image'; $limit_sql = ''; $count = intval($atts['count']); if ($count > 0) { $limit_sql = $wpdb->prepare('LIMIT %d', $count); } $sql = " SELECT m.id, m.memberID, m.name, m.firstName, m.lastName, m.imageID, i.filename AS photo FROM {$member_table} m LEFT JOIN {$image_table} i ON m.imageID = i.id WHERE m.imageID IS NOT NULL ORDER BY m.lastName, m.firstName {$limit_sql} "; $members = $wpdb->get_results($sql); if (!$members) { return '

No members found.

'; } ob_start(); echo '
'; foreach ($members as $m) { $photo = $m->photo ? esc_url($m->photo) : ''; $name = $m->name ? esc_html($m->name) : esc_html(trim($m->firstName . ' ' . $m->lastName)); $member_id = esc_attr($m->memberID); echo '
'; if ($photo) { echo '\"''; } echo '
' . $name . '
'; echo '
'; } echo '
'; return ob_get_clean(); } add_shortcode('tw_member_profile_gallery', 'twmp_member_profile_gallery_shortcode'); The Tin Whistles https://validator.w3.org/feed/docs/rss2.html TTW Home TWSF Home Tournament Sign-up Org Chart Test Page to figure out Golf Genius Leadership Statistics Calendar Organization Calendar Documents Membership Process Governance Documents Match Play Bracket Games Band of Brothers First Health Regional Hospital Habitat for Humanity Survey Results Scholars Scholar Gallery Majors and Memorials TYGA Committees Committees Password Reset Tournament of Champions Forgot Credentials Members member-dashboard Login Local Rules and Terms of Competition wpDataTable Members Page Tournament History