// 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 += '';
}
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;
}
?>
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');