'Journal Article',
'conference-paper' => 'Conference Paper',
'book-chapter' => 'Book Chapter',
'preprint' => 'Preprint',
'book' => 'Book',
'thesis' => 'Thesis',
'report' => 'Report',
'other' => 'Other',
];
public function register( Loader $loader ): void {
$loader->add_action( 'admin_menu', $this, 'add_menu_page' );
$loader->add_action( 'admin_post_acc_save_publication', $this, 'handle_save' );
$loader->add_action( 'admin_post_acc_delete_pub', $this, 'handle_delete' );
$loader->add_action( 'admin_post_acc_merge_publications', $this, 'handle_merge' );
}
private function get_linked_member_ids( int $pub_id ): array {
if ( ! $pub_id ) return [];
global $wpdb;
return array_map( 'intval', $wpdb->get_col( $wpdb->prepare(
"SELECT member_id FROM {$wpdb->prefix}academiccrew_member_publications WHERE publication_id = %d",
$pub_id
) ) );
}
private function refresh_member_stats( int $member_id ): void {
MemberStats::refresh( $member_id );
}
public function add_menu_page(): void {
add_submenu_page(
AdminMenu::PARENT_SLUG,
__( 'Publications', 'academiccrew' ),
__( 'Publications', 'academiccrew' ),
'edit_posts',
self::MENU_SLUG,
[ $this, 'render_page' ]
);
}
public function render_page(): void {
$action = sanitize_key( $_GET['action'] ?? 'list' );
if ( $action === 'merge' ) {
$this->render_merge_view();
} elseif ( $action === 'edit' || $action === 'new' ) {
$this->render_edit_form();
} else {
$this->render_list();
}
}
// -------------------------------------------------------------------------
// List view
// -------------------------------------------------------------------------
private function render_list(): void {
global $wpdb;
$search = sanitize_text_field( wp_unslash( $_GET['s'] ?? '' ) );
$type = sanitize_key( $_GET['type'] ?? '' );
$paged = max( 1, (int) ( $_GET['paged'] ?? 1 ) );
$offset = ( $paged - 1 ) * self::PER_PAGE;
$table = esc_sql( $wpdb->prefix . 'academiccrew_publications' );
// Sorting — column whitelist prevents SQL injection.
$allowed_orderby = [ 'id', 'title', 'year', 'pub_type', 'venue', 'citations', 'source_api', 'is_featured' ];
$orderby = in_array( $_GET['orderby'] ?? '', $allowed_orderby, true )
? sanitize_key( $_GET['orderby'] )
: '';
$order = strtoupper( $_GET['order'] ?? '' ) === 'ASC' ? 'ASC' : 'DESC';
$order_sql = esc_sql( $orderby ? "`{$orderby}` {$order}, id DESC" : 'year DESC, id DESC' );
// Featured / tag filters — only exposed when featured pubs exist.
$filter_featured = ! empty( $_GET['featured'] );
$filter_tag = (int) ( $_GET['tag_id'] ?? 0 );
$has_featured = (bool) $wpdb->get_var( "SELECT EXISTS(SELECT 1 FROM {$table} WHERE is_featured = 1)" ); // phpcs:ignore
$all_filter_tags = $has_featured ? ( $wpdb->get_results( // phpcs:ignore
"SELECT id, name, color FROM {$wpdb->prefix}academiccrew_pub_tags ORDER BY name ASC",
ARRAY_A
) ?: [] ) : [];
// Build WHERE dynamically
$conditions = [];
$params = [];
if ( $search ) {
$conditions[] = 'title LIKE %s';
$params[] = '%' . $wpdb->esc_like( $search ) . '%';
}
if ( $type ) {
$conditions[] = 'pub_type = %s';
$params[] = $type;
}
if ( $filter_featured ) {
$conditions[] = 'is_featured = 1';
}
if ( $filter_tag && $has_featured ) {
$rel_table = esc_sql( $wpdb->prefix . 'academiccrew_pub_tag_relations' );
$conditions[] = "id IN (SELECT publication_id FROM {$rel_table} WHERE tag_id = %d)"; // phpcs:ignore
$params[] = $filter_tag;
}
$where_sql = esc_sql( $conditions ? ( 'WHERE ' . implode( ' AND ', $conditions ) ) : '' );
if ( $params ) {
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$total = (int) $wpdb->get_var( $wpdb->prepare(
"SELECT COUNT(*) FROM {$table} {$where_sql}",
...$params
) );
$rows = $wpdb->get_results( $wpdb->prepare(
"SELECT id, title, year, pub_type, venue, citations, source_api, authors_json, is_featured
FROM {$table} {$where_sql}
ORDER BY {$order_sql}
LIMIT %d OFFSET %d",
...[ ...$params, self::PER_PAGE, $offset ]
), ARRAY_A );
// phpcs:enable
} else {
$total = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$table}" ); // phpcs:ignore
$rows = $wpdb->get_results( $wpdb->prepare( // phpcs:ignore
"SELECT id, title, year, pub_type, venue, citations, source_api, authors_json, is_featured
FROM {$table}
ORDER BY {$order_sql}
LIMIT %d OFFSET %d",
self::PER_PAGE, $offset
), ARRAY_A );
}
$total_pages = max( 1, (int) ceil( $total / self::PER_PAGE ) );
$base_url = admin_url( 'admin.php?page=' . self::MENU_SLUG );
$notice = $this->get_notice();
// Closure that renders a sortable
. Pass $style for width attributes.
$th = function ( string $col, string $label, string $style = '' ) use ( $orderby, $order, $base_url, $search, $type, $filter_featured, $filter_tag ): string {
$is_current = ( $orderby === $col );
$next_order = ( $is_current && $order === 'ASC' ) ? 'DESC' : 'ASC';
$url = add_query_arg( array_filter( [
'orderby' => $col,
'order' => $next_order,
's' => $search,
'type' => $type,
'featured' => $filter_featured ? '1' : '',
'tag_id' => $filter_tag ?: '',
] ), $base_url );
$arrow = $is_current ? ( $order === 'ASC' ? ' ▲' : ' ▼' ) : '';
$style_attr = $style ? ' style="' . esc_attr( $style ) . '"' : '';
return ' | '
. ''
. esc_html( $label )
. '' . $arrow . ''
. ' | ';
};
// Batch-fetch member names as fallback for publications with empty authors_json
$pub_ids_in_page = array_column( $rows ?: [], 'id' );
$member_name_map = [];
if ( ! empty( $pub_ids_in_page ) ) {
$placeholders = implode( ',', array_fill( 0, count( $pub_ids_in_page ), '%d' ) );
$mrows = $wpdb->get_results( $wpdb->prepare( // phpcs:ignore
"SELECT mp.publication_id, p.post_title
FROM {$wpdb->prefix}academiccrew_member_publications mp
INNER JOIN {$wpdb->posts} p ON mp.member_id = p.ID AND p.post_status = 'publish'
WHERE mp.publication_id IN ({$placeholders})", // phpcs:ignore
...$pub_ids_in_page
), ARRAY_A );
foreach ( $mrows as $mr ) {
$member_name_map[ (int) $mr['publication_id'] ][] = $mr['post_title'];
}
}
// Batch-fetch tag assignments for publications on this page.
$tag_map = []; // [pub_id => [[name, color], ...]]
if ( ! empty( $pub_ids_in_page ) ) {
$placeholders = implode( ',', array_fill( 0, count( $pub_ids_in_page ), '%d' ) );
$trows = $wpdb->get_results( $wpdb->prepare( // phpcs:ignore
"SELECT r.publication_id, t.name, t.color
FROM {$wpdb->prefix}academiccrew_pub_tag_relations r
INNER JOIN {$wpdb->prefix}academiccrew_pub_tags t ON t.id = r.tag_id
WHERE r.publication_id IN ({$placeholders})
ORDER BY t.name ASC",
...$pub_ids_in_page
), ARRAY_A );
foreach ( $trows as $tr ) {
$tag_map[ (int) $tr['publication_id'] ][] = $tr;
}
}
?>
()
|
|
self::MENU_SLUG, 'action' => 'edit', 'id' => $pub_id ],
admin_url( 'admin.php' )
);
$delete_url = wp_nonce_url(
admin_url( "admin-post.php?action=acc_delete_pub&id={$pub_id}" ),
'acc_delete_pub_' . $pub_id
);
$exclude_url = wp_nonce_url(
admin_url( "admin-post.php?action=acc_delete_pub&id={$pub_id}&acc_exclude=1" ),
'acc_delete_pub_' . $pub_id
);
$raw_authors = json_decode( $row['authors_json'] ?? '[]', true ) ?: [];
$authors = array_values( array_filter( array_map(
fn( $a ) => is_string( $a ) ? $a : ( $a['name'] ?? '' ),
$raw_authors
) ) );
if ( empty( $authors ) ) {
$authors = $member_name_map[ $pub_id ] ?? [];
}
$type_lbl = self::PUB_TYPES[ $row['pub_type'] ?? '' ] ?? $row['pub_type'] ?? '';
?>
|
3 ) echo ' …';
?>
|
|
|
|
|
|
★
|
1 ) :
$paginate_base = add_query_arg(
array_filter( [
's' => $search,
'type' => $type,
'featured' => $filter_featured ? '1' : '',
'tag_id' => $filter_tag ?: '',
] ),
$base_url
);
echo '
';
echo wp_kses_post( paginate_links( [
'base' => add_query_arg( 'paged', '%#%', $paginate_base ),
'format' => '',
'prev_text' => '«',
'next_text' => '»',
'total' => $total_pages,
'current' => $paged,
] ) ?? '' );
echo '
';
endif; ?>
prefix . 'academiccrew_publications' );
if ( $pub_id ) {
$pub = $wpdb->get_row( $wpdb->prepare(
"SELECT * FROM {$table} WHERE id = %d",
$pub_id
), ARRAY_A );
if ( ! $pub ) {
wp_die( esc_html__( 'Publication not found.', 'academiccrew' ) );
}
} else {
$pub = [];
}
$list_url = admin_url( 'admin.php?page=' . self::MENU_SLUG );
$notice = $this->get_notice();
$get = fn( string $k, string $d = '' ) => $pub[ $k ] ?? $d;
// Fetch all tags + current assignment for this pub.
$all_tags = $wpdb->get_results(
"SELECT id, name, color FROM {$wpdb->prefix}academiccrew_pub_tags ORDER BY name ASC",
ARRAY_A
) ?: [];
$current_tag_ids = $pub_id ? array_map( 'intval', $wpdb->get_col( $wpdb->prepare(
"SELECT tag_id FROM {$wpdb->prefix}academiccrew_pub_tag_relations WHERE publication_id = %d",
$pub_id
) ) ) : [];
// Build ordered author list (unified: members + external, preserving order)
$raw_auth = json_decode( $pub['authors_json'] ?? '[]', true ) ?: [];
$authors_form = array_map( function ( $a ) {
if ( is_string( $a ) ) return [ 'name' => $a, 'member_id' => 0 ];
return [ 'name' => (string) ( $a['name'] ?? '' ), 'member_id' => (int) ( $a['member_id'] ?? 0 ) ];
}, $raw_auth );
// Append linked members absent from authors_json (backwards compat with old synced pubs)
$listed_mem_ids = array_column( $authors_form, 'member_id' );
foreach ( $this->get_linked_member_ids( $pub_id ) as $lid ) {
if ( $lid > 0 && ! in_array( $lid, $listed_mem_ids, true ) ) {
$mp = get_post( $lid );
if ( $mp ) $authors_form[] = [ 'name' => $mp->post_title, 'member_id' => $lid ];
}
}
// Members list for JS autocomplete
$members_for_ac = get_posts( [
'post_type' => 'academiccrew_member',
'post_status' => 'publish',
'posts_per_page' => -1,
'orderby' => 'title',
'order' => 'ASC',
] );
$members_js = wp_json_encode( array_map(
fn( $m ) => [ 'id' => $m->ID, 'name' => $m->post_title ],
$members_for_ac
) );
?>
prefix . 'academiccrew_publications' );
// Build ordered author list from parallel arrays
$author_names = (array) ( $_POST['pub_author_name'] ?? [] );
$author_mem_ids = (array) ( $_POST['pub_author_member_id'] ?? [] );
$authors_list = [];
$new_member_ids = [];
foreach ( $author_names as $i => $raw_name ) {
$name = sanitize_text_field( wp_unslash( $raw_name ) );
if ( ! $name ) continue;
$mid = (int) ( $author_mem_ids[ $i ] ?? 0 );
$entry = [ 'name' => $name ];
if ( $mid > 0 ) {
$entry['member_id'] = $mid;
$new_member_ids[] = $mid;
}
$authors_list[] = $entry;
}
$authors_json = wp_json_encode( $authors_list );
$year = ( $_POST['pub_year'] ?? '' ) !== '' ? (int) wp_unslash( $_POST['pub_year'] ) : null;
$citations = ( $_POST['pub_citations'] ?? '' ) !== '' ? (int) wp_unslash( $_POST['pub_citations'] ) : null;
// Keywords — comma-separated input → JSON array
$kw_raw = sanitize_text_field( wp_unslash( $_POST['pub_keywords'] ?? '' ) );
$kw_arr = $kw_raw
? array_values( array_filter( array_map( 'trim', explode( ',', $kw_raw ) ) ) )
: [];
$keywords_json = $kw_arr ? wp_json_encode( $kw_arr ) : null;
$data = [
'title' => sanitize_text_field( wp_unslash( $_POST['pub_title'] ?? '' ) ),
'abstract' => sanitize_textarea_field( wp_unslash( $_POST['pub_abstract'] ?? '' ) ),
'venue' => sanitize_text_field( wp_unslash( $_POST['pub_venue'] ?? '' ) ) ?: null,
'pub_type' => sanitize_key( $_POST['pub_type'] ?? '' ) ?: null,
'doi' => sanitize_text_field( wp_unslash( $_POST['pub_doi'] ?? '' ) ) ?: null,
'pdf_url' => esc_url_raw( wp_unslash( $_POST['pub_pdf_url'] ?? '' ) ) ?: null,
'url' => esc_url_raw( wp_unslash( $_POST['pub_url'] ?? '' ) ) ?: null,
'keywords' => $keywords_json,
'authors_json' => $authors_json,
'updated_at' => current_time( 'mysql', true ),
];
// Use raw SQL SET for nullable integers to avoid wpdb turning null into 0
$set_clauses = [];
foreach ( [
'title' => '%s',
'abstract' => '%s',
'venue' => '%s',
'pub_type' => '%s',
'doi' => '%s',
'pdf_url' => '%s',
'url' => '%s',
'authors_json' => '%s',
'keywords' => '%s',
'updated_at' => '%s',
] as $col => $fmt ) {
if ( $data[ $col ] === null ) {
$set_clauses[] = "`{$col}` = NULL";
} else {
$set_clauses[] = $wpdb->prepare( "`{$col}` = {$fmt}", $data[ $col ] ); // phpcs:ignore
}
}
// year and citations — nullable integers
$set_clauses[] = $year !== null ? $wpdb->prepare( '`year` = %d', $year ) : '`year` = NULL';
$set_clauses[] = $citations !== null ? $wpdb->prepare( '`citations` = %d', $citations ) : '`citations` = NULL';
// A count typed here is an editor's correction: syncs must not overwrite it.
$old_citations = $pub_id ? $wpdb->get_var( $wpdb->prepare( "SELECT citations FROM {$table} WHERE id = %d", $pub_id ) ) : null; // phpcs:ignore
if ( $citations === null ) {
$set_clauses[] = '`citations_source` = NULL';
} elseif ( $old_citations === null || (int) $old_citations !== $citations ) {
$set_clauses[] = "`citations_source` = 'manual'";
}
// featured flags
$is_featured = ! empty( $_POST['pub_is_featured'] ) ? 1 : 0;
$featured_order = ( $_POST['pub_featured_order'] ?? '' ) !== '' ? max( 1, min( 999, (int) $_POST['pub_featured_order'] ) ) : 999;
$set_clauses[] = $wpdb->prepare( '`is_featured` = %d', $is_featured );
$set_clauses[] = $wpdb->prepare( '`featured_order` = %d', $featured_order );
$set_sql = implode( ', ', $set_clauses );
if ( $pub_id ) {
$wpdb->query( $wpdb->prepare( // phpcs:ignore
"UPDATE {$table} SET {$set_sql} WHERE id = %d", // phpcs:ignore
$pub_id
) );
$redirect_id = $pub_id;
$msg = 'updated';
} else {
$set_clauses[] = $wpdb->prepare( '`created_at` = %s', current_time( 'mysql', true ) );
$set_sql = implode( ', ', $set_clauses );
$wpdb->query( "INSERT INTO {$table} SET {$set_sql}" ); // phpcs:ignore
$redirect_id = $wpdb->insert_id;
$msg = 'added';
}
// Sync member-publication links and refresh stats
$rel_table = esc_sql( $wpdb->prefix . 'academiccrew_member_publications' );
$prev_member_ids = $this->get_linked_member_ids( $redirect_id );
$new_member_ids = array_unique( array_filter( $new_member_ids ) );
$wpdb->query( $wpdb->prepare(
"DELETE FROM {$rel_table} WHERE publication_id = %d",
$redirect_id
) );
foreach ( $new_member_ids as $mid ) {
$wpdb->query( $wpdb->prepare(
"INSERT IGNORE INTO {$rel_table} (member_id, publication_id) VALUES (%d, %d)",
$mid, $redirect_id
) );
}
foreach ( array_unique( array_merge( $prev_member_ids, $new_member_ids ) ) as $mid ) {
$this->refresh_member_stats( (int) $mid );
}
// Sync tag relations — tags are only valid for featured publications.
$tag_rel_table = esc_sql( $wpdb->prefix . 'academiccrew_pub_tag_relations' );
$wpdb->query( $wpdb->prepare(
"DELETE FROM {$tag_rel_table} WHERE publication_id = %d",
$redirect_id
) );
if ( $is_featured ) {
$submitted_tag_ids = array_unique( array_filter( array_map( 'intval', (array) ( $_POST['pub_tag_ids'] ?? [] ) ) ) );
if ( $submitted_tag_ids ) {
$placeholders = implode( ',', array_fill( 0, count( $submitted_tag_ids ), '%d' ) );
$valid_tag_ids = $wpdb->get_col( $wpdb->prepare( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
"SELECT id FROM {$wpdb->prefix}academiccrew_pub_tags WHERE id IN ({$placeholders})", // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
...$submitted_tag_ids
) );
foreach ( array_map( 'intval', $valid_tag_ids ) as $tid ) {
$wpdb->query( $wpdb->prepare(
"INSERT IGNORE INTO {$tag_rel_table} (publication_id, tag_id) VALUES (%d, %d)",
$redirect_id, $tid
) );
}
}
}
wp_safe_redirect( add_query_arg(
[ 'page' => self::MENU_SLUG, 'action' => 'edit', 'id' => $redirect_id, 'acc_msg' => $msg ],
admin_url( 'admin.php' )
) );
exit;
}
public function handle_delete(): void {
$pub_id = (int) ( $_GET['id'] ?? 0 );
$exclude = ! empty( $_GET['acc_exclude'] );
if ( ! $pub_id
|| ! wp_verify_nonce( sanitize_key( $_GET['_wpnonce'] ?? '' ), 'acc_delete_pub_' . $pub_id )
|| ! current_user_can( 'edit_posts' ) ) {
wp_die( esc_html__( 'Not allowed.', 'academiccrew' ) );
}
global $wpdb;
if ( $exclude ) {
$pub = $wpdb->get_row( $wpdb->prepare(
"SELECT doi, orcid_work_id, scholar_id, iris_id, title, year
FROM {$wpdb->prefix}academiccrew_publications WHERE id = %d",
$pub_id
), ARRAY_A );
if ( $pub ) {
$wpdb->insert(
$wpdb->prefix . 'academiccrew_excluded_publications',
[
'doi' => $pub['doi'] ?: null,
'orcid_work_id' => $pub['orcid_work_id'] ?: null,
'scholar_id' => $pub['scholar_id'] ?: null,
'iris_id' => $pub['iris_id'] ?: null,
'title' => $pub['title'] ?: null,
'year' => $pub['year'] ? (int) $pub['year'] : null,
'excluded_by' => get_current_user_id() ?: null,
],
[ '%s', '%s', '%s', '%s', '%s', '%d', '%d' ]
);
}
}
$wpdb->delete( $wpdb->prefix . 'academiccrew_publications', [ 'id' => $pub_id ], [ '%d' ] );
$wpdb->delete( $wpdb->prefix . 'academiccrew_member_publications', [ 'publication_id' => $pub_id ], [ '%d' ] );
$msg = $exclude ? 'deleted-excluded' : 'deleted';
wp_safe_redirect( add_query_arg(
'acc_msg', $msg,
admin_url( 'admin.php?page=' . self::MENU_SLUG )
) );
exit;
}
// -------------------------------------------------------------------------
// Helpers
// -------------------------------------------------------------------------
private function get_notice(): ?array {
$msg = sanitize_key( $_GET['acc_msg'] ?? '' );
return match ( $msg ) {
'updated' => [ 'type' => 'success', 'message' => __( 'Publication updated.', 'academiccrew' ) ],
'added' => [ 'type' => 'success', 'message' => __( 'Publication added.', 'academiccrew' ) ],
'deleted' => [ 'type' => 'success', 'message' => __( 'Publication deleted.', 'academiccrew' ) ],
'deleted-excluded' => [ 'type' => 'success', 'message' => __( 'Publication deleted and excluded from future syncs.', 'academiccrew' ) ],
'merged' => [ 'type' => 'success', 'message' => __( 'Publications merged successfully.', 'academiccrew' ) ],
'merge-error' => [ 'type' => 'error', 'message' => __( 'Merge failed. Please check the IDs and try again.', 'academiccrew' ) ],
default => null,
};
}
private function type_style( string $type ): string {
$colors = [
'journal-article' => 'background:#E6F1FB;color:#0C447C',
'conference-paper' => 'background:#EEEDFE;color:#3C3489',
'book-chapter' => 'background:#EAF3DE;color:#3B6D11',
'preprint' => 'background:#F1EFE8;color:#444441',
'book' => 'background:#EAF3DE;color:#3B6D11',
];
$base = 'font-size:10px;font-weight:500;padding:2px 7px;border-radius:8px;white-space:nowrap;display:inline-block';
return $base . ';' . ( $colors[ $type ] ?? 'background:#f0f0f0;color:#666' );
}
// -------------------------------------------------------------------------
// Merge view
// -------------------------------------------------------------------------
private function render_merge_view(): void {
global $wpdb;
$pub_id = (int) ( $_GET['id'] ?? 0 );
$table = esc_sql( $wpdb->prefix . 'academiccrew_publications' );
if ( ! $pub_id ) {
wp_die( esc_html__( 'No publication specified.', 'academiccrew' ) );
}
$pub = $wpdb->get_row( $wpdb->prepare(
"SELECT id, title, year, venue, doi, source_api, scholar_id, orcid_work_id, iris_id FROM {$table} WHERE id = %d",
$pub_id
), ARRAY_A );
if ( ! $pub ) {
wp_die( esc_html__( 'Publication not found.', 'academiccrew' ) );
}
$list_url = admin_url( 'admin.php?page=' . self::MENU_SLUG );
$search_q = sanitize_text_field( wp_unslash( $_GET['merge_search'] ?? '' ) );
$search_id = (int) ( $_GET['merge_id'] ?? 0 );
$candidates = [];
if ( $search_q ) {
$candidates = $wpdb->get_results( $wpdb->prepare(
"SELECT id, title, year, venue, doi, source_api FROM {$table}
WHERE id != %d
AND (title LIKE %s OR doi LIKE %s)
ORDER BY year DESC, id DESC
LIMIT 20",
$pub_id,
'%' . $wpdb->esc_like( $search_q ) . '%',
'%' . $wpdb->esc_like( $search_q ) . '%'
), ARRAY_A );
} elseif ( $search_id > 0 ) {
$candidates = $wpdb->get_results( $wpdb->prepare(
"SELECT id, title, year, venue, doi, source_api FROM {$table}
WHERE id = %d AND id != %d",
$search_id,
$pub_id
), ARRAY_A );
}
// If exactly one candidate is selected for merging, show confirmation.
$confirm_id = (int) ( $_GET['confirm_with'] ?? 0 );
$confirm_pub = null;
if ( $confirm_id ) {
$confirm_pub = $wpdb->get_row( $wpdb->prepare(
"SELECT id, title, year, venue, doi, source_api, scholar_id, orcid_work_id, iris_id FROM {$table} WHERE id = %d AND id != %d",
$confirm_id,
$pub_id
), ARRAY_A );
}
// Suggest authoritative record: prefer the one with a DOI.
$suggested_keep = $pub['doi'] ? (int) $pub['id'] : ( $confirm_pub && $confirm_pub['doi'] ? (int) $confirm_pub['id'] : (int) $pub['id'] );
?>
merge_publications( $keep_id, $drop_id );
// Refresh stats for all members linked to either publication.
if ( $success ) {
$rel_table = esc_sql( $wpdb->prefix . 'academiccrew_member_publications' );
$member_ids = $wpdb->get_col( $wpdb->prepare(
"SELECT DISTINCT member_id FROM {$rel_table} WHERE publication_id = %d",
$keep_id
) );
foreach ( $member_ids as $mid ) {
$this->refresh_member_stats( (int) $mid );
}
}
$msg = $success ? 'merged' : 'merge-error';
wp_safe_redirect( add_query_arg( 'acc_msg', $msg, admin_url( 'admin.php?page=' . self::MENU_SLUG ) ) );
exit;
}
}