// 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');