// — Helpers (declared once, safe to redefine-check) —
if (!function_exists(\’atws_normalize_lower\’)) {
function atws_normalize_lower(string $s): string {
// Trim, collapse whitespace, strip trailing punctuation, lowercase
$s = trim(preg_replace(\’/\\s+/u\’, \’ \’, $s));
$s = preg_replace(\’/[.,]+$/u\’, \’\’, $s);
return function_exists(\’mb_strtolower\’) ? mb_strtolower($s, \’UTF-8\’) : strtolower($s);
}
}
if (!function_exists(\’atws_strip_suffixes\’)) {
function atws_strip_suffixes(array $parts): array {
if (!$parts) return $parts;
$suffixes = [
\’jr\’,\’sr\’,\’ii\’,\’iii\’,\’iv\’,\’v\’,\’vi\’,
\’phd\’,\’m.d\’,\’md\’,\’esq\’,\’jd\’,\’mba\’,\’dvm\’,\’dds\’,\’do\’
];
// Normalize a token for matching (lowercase, strip trailing dots/commas)
$norm = function($t) {
$t = preg_replace(\’/[.,]+$/u\’, \’\’, $t);
return function_exists(\’mb_strtolower\’) ? mb_strtolower($t, \’UTF-8\’) : strtolower($t);
};
while (count($parts) > 1) {
$last = $norm(end($parts));
if (in_array($last, $suffixes, true)) {
array_pop($parts);
} else {
break;
}
}
return $parts;
}
}
if (!function_exists(\’atws_extract_last_name\’)) {
function atws_extract_last_name(string $fullName): string {
$name = trim(preg_replace(\’/\\s+/u\’, \’ \’, $fullName));
if ($name === \’\’) return \’\’;
// If \”Lastname, Firstname …\” -> take text before first comma as last name
if (strpos($name, \’,\’) !== false) {
$last = trim(strtok($name, \’,\’));
return atws_normalize_lower($last);
}
$parts = preg_split(\’/\\s+/u\’, $name, -1, PREG_SPLIT_NO_EMPTY);
if (!$parts) return \’\’;
// Remove suffixes like \”Jr.\”, \”III\”, \”PhD\”, etc.
$parts = atws_strip_suffixes($parts);
$n = count($parts);
if ($n === 1) {
return atws_normalize_lower($parts[0]);
}
// Known particles
$single_particles = [
\’da\’,\’de\’,\’del\’,\’della\’,\’der\’,\’di\’,\’la\’,\’le\’,\’du\’,\’van\’,\’von\’,\’st\’,\’st.\’,\’saint\’,\’bin\’,\’ibn\’,\’al\’
];
// Two-word particles that can precede the surname (right before last token)
$two_word_particles = [
\’de la\’,\’de las\’,\’de los\’,\’van der\’,\’von der\’,\’van de\’,\’van den\’,\’von den\’,\’st. john\’,\’saint john\’
];
$last = $parts[$n – 1];
$pre1 = $n >= 2 ? atws_normalize_lower($parts[$n – 2]) : \’\’;
$pre2 = $n >= 3 ? atws_normalize_lower($parts[$n – 3]) : \’\’;
// Check two-word particle immediately before surname
if ($n >= 3) {
$combo = $pre2 . \’ \’ . $pre1;
if (in_array($combo, $two_word_particles, true)) {
$last = $parts[$n – 3] . \’ \’ . $parts[$n – 2] . \’ \’ . $parts[$n – 1];
return atws_normalize_lower($last);
}
}
// Check single-word particle immediately before surname
if ($n >= 2 && in_array($pre1, $single_particles, true)) {
$last = $parts[$n – 2] . \’ \’ . $parts[$n – 1];
}
return atws_normalize_lower($last);
}
}
// — Shortcode —
add_action(\’init\’, function () {
add_shortcode(\’instructor_list\’, function ($atts = []) {
if (!function_exists(\’get_field\’)) {
return \’\’;
}
$atts = shortcode_atts([
\’include_drafts\’ => false,
], $atts, \’instructor_list\’);
$include_drafts = filter_var($atts[\’include_drafts\’], FILTER_VALIDATE_BOOLEAN);
// Hook to override permissions for drafts
if ($include_drafts) {
add_filter(\’posts_where\’, function ($where, $query) {
global $wpdb;
// Only affect our specific shortcode query
if (!empty($query->query_vars[\’force_show_drafts\’])) {
// Expand publish -> publish|draft
$where = str_replace(
\”AND ({$wpdb->posts}.post_status = \’publish\’\”,
\”AND ({$wpdb->posts}.post_status IN (\’publish\’,\’draft\’)\”,
$where
);
}
return $where;
}, 10, 2);
}
$args = [
\’post_type\’ => \’course\’,
\’posts_per_page\’ => -1,
\’post_status\’ => $include_drafts ? [\’publish\’,\’draft\’] : [\’publish\’],
\’force_show_drafts\’ => $include_drafts, // Flag for our filter
\’suppress_filters\’ => false,
];
$query = new WP_Query($args);
$instructors = []; // [\’Display Name\’ => \’Bio\’]
if ($query->have_posts()) {
while ($query->have_posts()) {
$query->the_post();
$name = trim((string) get_field(\’instructor\’));
$bio = trim((string) get_field(\’instructor_bio\’));
if ($name && $bio && !isset($instructors[$name])) {
$instructors[$name] = $bio;
}
}
wp_reset_postdata();
}
if (empty($instructors)) {
return \’\’;
}
// Sort keys (names) by extracted last name, then by full display name
$names = array_keys($instructors);
usort($names, function ($a, $b) {
$la = atws_extract_last_name($a);
$lb = atws_extract_last_name($b);
if ($la === $lb) {
$cmp = strcasecmp($a, $b);
return $cmp === 0 ? strcmp($a, $b) : $cmp;
}
return $la <=> $lb;
});
// Build output in sorted order
$output = \’
foreach ($names as $name) {
$bio = $instructors[$name];
$output .= \’
$output .= \’
\’ . esc_html($name) . \’
\’;
$output .= \’
\’ . esc_html($bio) . \’
\’;
$output .= \’
\’;
}
$output .= \’
\’;
return $output;
});
});