add_filter( 'the_content', $this, 'maybe_inject', 11 );
$loader->add_action( 'wp_enqueue_scripts', $this, 'enqueue_assets' );
$loader->add_filter( 'body_class', $this, 'add_body_class' );
}
public function add_body_class( array $classes ): array {
if ( is_singular( 'page' )
&& (int) get_post_meta( get_queried_object_id(), '_academiccrew_member_id', true ) ) {
$classes[] = 'acc-member-profile-page';
}
return $classes;
}
public function enqueue_assets(): void {
if ( ! is_singular( 'page' ) ) {
return;
}
$member_id = (int) get_post_meta( get_queried_object_id(), '_academiccrew_member_id', true );
if ( ! $member_id ) {
return;
}
wp_enqueue_style(
'academiccrew-member-profile',
ACADEMICCREW_URL . 'assets/css/member-profile.css',
[ 'fusion-stylesheet', 'tabler-icons' ],
ACADEMICCREW_VERSION
);
wp_enqueue_script(
'academiccrew-member-profile',
ACADEMICCREW_URL . 'assets/js/member-profile.js',
[],
ACADEMICCREW_VERSION,
true
);
}
public function maybe_inject( string $content ): string {
if ( ! is_singular( 'page' ) || ! in_the_loop() ) {
return $content;
}
$page_id = get_the_ID();
$member_id = (int) get_post_meta( $page_id, '_academiccrew_member_id', true );
if ( ! $member_id ) {
return $content;
}
static $injected = [];
if ( isset( $injected[ $page_id ] ) ) {
return $content;
}
$injected[ $page_id ] = true;
return $this->render_hero( $member_id )
. $this->render_publications( $member_id )
. $this->render_collaborations( $member_id )
. $this->render_service_and_awards( $member_id )
. $content;
}
// ---------------------------------------------------------------
// Zone 1: Hero
// ---------------------------------------------------------------
private function render_hero( int $member_id ): string {
$get = fn( string $key ) => get_post_meta( $member_id, $key, true );
$name = get_the_title( $member_id );
$role = $get( '_acc_member_role' ) ?: 'collaborator';
// The group's own default department (Settings) is implied — not worth a line.
$department = (string) ( $get( '_acc_department' ) ?: '' );
$org_name = (string) ( get_option( 'academiccrew_settings', [] )['org_name'] ?? '' );
if ( $org_name !== '' && 0 === strcasecmp( trim( $department ), $org_name ) ) {
$department = '';
}
$bio = $get( '_acc_bio' ) ?: '';
$email = $get( '_acc_email' ) ?: '';
$avatar_url = $get( '_acc_avatar_url' )
?: ( get_the_post_thumbnail_url( $member_id, [ 96, 96 ] ) ?: '' );
$orcid_id = $get( '_acc_orcid_id' ) ?: '';
$scopus_id = $get( '_acc_scopus_id' ) ?: '';
$scholar_id = $get( '_acc_scholar_id' ) ?: '';
$iris_url = $get( '_acc_iris_profile_url' ) ?: '';
if ( ! $iris_url ) {
$iris_id = $get( '_acc_iris_person_id' );
$iris_base = untrailingslashit( (string) ( get_option( 'academiccrew_settings', [] )['iris_base_url'] ?? '' ) );
$iris_url = ( $iris_id && $iris_base ) ? "{$iris_base}/cris/rp/{$iris_id}" : '';
}
$personal_web = $get( '_acc_personal_website' ) ?: '';
$linkedin = $get( '_acc_linkedin_url' ) ?: '';
$github = $get( '_acc_github_url' ) ?: '';
$twitter = $get( '_acc_twitter_url' ) ?: '';
$phone = $get( '_acc_phone' ) ?: '';
$office = $get( '_acc_office' ) ?: '';
$areas_raw = $get( '_acc_research_areas' );
$areas = is_array( $areas_raw )
? $areas_raw
: array_filter( array_map( 'trim', explode( ',', $areas_raw ?: '' ) ) );
$pub_count = (int) ( $get( '_acc_pub_count' ) ?: 0 );
$citations = (int) ( $get( '_acc_citation_count' ) ?: 0 );
$h_index = (int) ( $get( '_acc_h_index' ) ?: 0 );
$cit_url = MemberStats::citation_url( $member_id );
$stat_tag = $cit_url ? 'a' : 'span';
$stat_attrs = $cit_url
? sprintf( ' href="%s" target="_blank" rel="noopener noreferrer" title="%s"',
esc_url( $cit_url ), esc_attr__( 'Source: Google Scholar', 'academiccrew' ) )
: '';
$course_count = $this->count_member_courses( $member_id );
$colors = RoleRegistry::colors()[ $role ] ?? [ 'bg' => '#f0f0f0', 'text' => '#333' ];
$portrait_class = 'acc-portrait' . ( $avatar_url ? '' : ' is-initials' );
$archive_id = (int) get_option( 'academiccrew_members_archive_page_id', 0 );
$archive_url = $archive_id ? get_permalink( $archive_id ) : null;
ob_start();
?>
; ?>)
initials( $name ) ); ?>
$linkedin, 'github' => $github, 'twitter' => $twitter ] );
if ( $socials ) : ?>
$url ) :
// Tabler's "brand-twitter" is the retired bird logo; "brand-x" is the current one.
$icon = 'twitter' === $net ? 'x' : $net;
$label = 'twitter' === $net ? 'X' : ucfirst( $net );
?>
< class="stat">
>
< class="stat">
h-index
>
$member_id,
'restUrl' => rest_url( "academiccrew/v1/members/{$member_id}/publications" ),
'filtersUrl' => rest_url( "academiccrew/v1/members/{$member_id}/publications/filters" ),
'nonce' => wp_create_nonce( 'wp_rest' ),
'perPage' => 10,
'i18n' => [
'publications' => __( 'Publications', 'academiccrew' ),
'inPress' => __( 'In press', 'academiccrew' ),
'searchPlaceholder' => __( 'Search title or venue...', 'academiccrew' ),
'all' => __( 'All', 'academiccrew' ),
'noResults' => __( 'No publications match these filters.', 'academiccrew' ),
'loading' => __( 'Loading...', 'academiccrew' ),
'sortYearDesc' => __( 'Year ↓', 'academiccrew' ),
'sortYearAsc' => __( 'Year ↑', 'academiccrew' ),
'sortCitations' => __( 'Citations ↓', 'academiccrew' ),
'sortCitationsAsc' => __( 'Citations ↑', 'academiccrew' ),
'sortTitle' => __( 'Title A–Z', 'academiccrew' ),
'sortTitleDesc' => __( 'Title Z–A', 'academiccrew' ),
'showing' => __( 'Showing', 'academiccrew' ),
'of' => __( 'of', 'academiccrew' ),
'doi' => 'DOI',
'pdf' => 'PDF',
'etAl' => 'et al.',
'copyBibtex' => __( 'Copy BibTeX', 'academiccrew' ),
'bibtexCopied' => __( 'BibTeX copied!', 'academiccrew' ),
'corresponding' => __( 'Corresponding author', 'academiccrew' ),
'featuredOnly' => __( 'Featured only', 'academiccrew' ),
'allTags' => __( 'All tags', 'academiccrew' ),
'typeLabels' => [
'journal-article' => __( 'Journal', 'academiccrew' ),
'conference-paper' => __( 'Conference', 'academiccrew' ),
'book-chapter' => __( 'Book chapter', 'academiccrew' ),
'preprint' => __( 'Preprint', 'academiccrew' ),
'book' => __( 'Book', 'academiccrew' ),
'thesis' => __( 'Thesis', 'academiccrew' ),
'report' => __( 'Report', 'academiccrew' ),
'other' => __( 'Other', 'academiccrew' ),
],
],
] );
// The member's own picks, above the full list.
$selected_html = ( new \AcademicCrew\Blocks\FeaturedPublicationsBlock() )->render( [ 'count' => 10, 'member' => $member_id ] );
ob_start();
?>
'acc_collaboration',
'post_status' => 'publish',
'posts_per_page' => -1,
'orderby' => 'title',
'order' => 'ASC',
'meta_query' => [
'relation' => 'OR',
[ // new format: [{member_id: X, role: Y}, ...]
'key' => '_acc_linked_members',
'value' => '"member_id";i:' . $member_id . ';',
'compare' => 'LIKE',
],
[ // old format: [X, Y, Z] (flat int array, backward compat)
'key' => '_acc_linked_members',
'value' => ':' . $member_id . ';',
'compare' => 'LIKE',
],
],
] );
if ( empty( $projects ) ) {
return '';
}
ob_start();
?>
get_post_meta( $project->ID, $k, true );
$start = (string) ( $get( '_acc_start_date' ) ?: '' );
$end = (string) ( $get( '_acc_end_date' ) ?: '' );
$ptype = (string) ( $get( '_acc_project_type' ) ?: '' );
$thumbnail = get_the_post_thumbnail_url( $project->ID, 'medium' ) ?: '';
$kw_raw = $get( '_acc_keywords' );
$keywords = is_array( $kw_raw ) ? $kw_raw : [];
$short_desc = (string) ( $get( '_acc_short_desc' ) ?: '' );
$linked_page = (int) get_post_meta( $project->ID, '_academiccrew_linked_page_id', true );
$url = $linked_page ? get_permalink( $linked_page ) : get_permalink( $project->ID );
$dates = '';
if ( $start && $end ) {
$dates = date_i18n( 'Y', strtotime( $start ) ) . '–' . date_i18n( 'Y', strtotime( $end ) );
} elseif ( $start ) {
$dates = date_i18n( 'Y', strtotime( $start ) ) . '–' . esc_html__( 'ongoing', 'academiccrew' );
}
$status = ( $end && strtotime( $end ) < time() ) ? 'completed' : 'ongoing';
?>
post_title, 0, 1 ) ) ); ?>
__( 'Chair', 'academiccrew' ),
'area-chair' => __( 'Area Chair', 'academiccrew' ),
'senior-pc' => __( 'Senior PC', 'academiccrew' ),
'tpc' => __( 'TPC', 'academiccrew' ),
'organiser' => __( 'Organiser', 'academiccrew' ),
'editor' => __( 'Editor', 'academiccrew' ),
// Backwards compat with the old schema keys:
'tpc-member' => __( 'TPC', 'academiccrew' ),
'editorial-board' => __( 'Editor', 'academiccrew' ),
'reviewer' => __( 'Reviewer', 'academiccrew' ),
];
$has_roles = ! empty( $roles );
$has_awards = ! empty( $awards );
$grid_class = ( $has_roles && $has_awards ) ? 'acc-member-extra-grid' : 'acc-member-extra-single';
ob_start();
?>
post_content ) ) {
return '';
}
return wpautop( do_shortcode( do_blocks( $post->post_content ) ) );
}
private function count_member_courses( int $member_id ): int {
$courses = get_posts( [
'post_type' => 'academiccrew_course',
'post_status' => 'publish',
'posts_per_page' => -1,
'fields' => 'ids',
'meta_query' => [ [
'key' => '_acc_instructors',
'value' => '"member_id";i:' . $member_id,
'compare' => 'LIKE',
] ],
] );
return count( $courses );
}
private function format_phone( string $raw ): string {
$digits = preg_replace( '/\D/', '', $raw );
if ( strlen( $digits ) === 10 && str_starts_with( $digits, '011' ) ) {
return '+39 ' . substr( $digits, 0, 3 ) . ' ' . substr( $digits, 3, 3 ) . ' ' . substr( $digits, 6 );
}
return $raw;
}
private function initials( string $name ): string {
$parts = array_values( array_filter( preg_split( '/\s+/', trim( $name ) ) ) );
return strtoupper( implode( '', array_map(
fn( $w ) => mb_substr( $w, 0, 1 ),
array_slice( $parts, 0, 2 )
) ) );
}
}