add_action( 'admin_menu', $this, 'add_menu_page' );
$loader->add_action( 'admin_post_acc_save_pub_tag', $this, 'handle_save' );
$loader->add_action( 'admin_post_acc_delete_pub_tag', $this, 'handle_delete' );
}
public function add_menu_page(): void {
add_submenu_page(
AdminMenu::PARENT_SLUG,
__( 'Publication Tags', 'academiccrew' ),
__( 'Pub. Tags', 'academiccrew' ),
'edit_posts',
self::MENU_SLUG,
[ $this, 'render_page' ]
);
}
public function render_page(): void {
$action = sanitize_key( $_GET['action'] ?? '' );
$tag_id = (int) ( $_GET['id'] ?? 0 );
if ( $action === 'delete-confirm' && $tag_id ) {
$this->render_delete_confirm( $tag_id );
return;
}
$this->render_list( $action === 'edit' ? $tag_id : 0 );
}
// -------------------------------------------------------------------------
// List + add/edit form
// -------------------------------------------------------------------------
private function render_list( int $editing_id ): void {
global $wpdb;
$tags_table = esc_sql( $wpdb->prefix . 'academiccrew_pub_tags' );
$rel_table = esc_sql( $wpdb->prefix . 'academiccrew_pub_tag_relations' );
// Fetch all tags with usage count.
$tags = $wpdb->get_results(
"SELECT t.id, t.name, t.color,
COUNT(r.publication_id) AS pub_count
FROM {$tags_table} t
LEFT JOIN {$rel_table} r ON r.tag_id = t.id
GROUP BY t.id
ORDER BY t.name ASC",
ARRAY_A
) ?: [];
$editing = null;
if ( $editing_id ) {
foreach ( $tags as $t ) {
if ( (int) $t['id'] === $editing_id ) {
$editing = $t;
break;
}
}
}
$base_url = admin_url( 'admin.php?page=' . self::MENU_SLUG );
$notice = $this->get_notice();
?>
|
|
|
|
'edit', 'id' => $tid ], $base_url );
$del_url = add_query_arg(
[ 'action' => 'delete-confirm', 'id' => $tid,
'_wpnonce' => wp_create_nonce( 'acc_del_tag_confirm_' . $tid ) ],
$base_url
);
$is_editing = ( $editing_id === $tid );
?>
>
|
|
|
|
|
(function () {
'use strict';
var colorInput = document.getElementById('acc_tag_color');
var nameInput = document.getElementById('acc_tag_name');
var preview = document.getElementById('acc-tag-color-preview');
var swatches = document.querySelectorAll('.acc-tag-swatch');
function updatePreview() {
if (preview) {
preview.style.background = colorInput.value;
preview.textContent = nameInput.value.trim() || ;
}
}
function setColor(hex) {
colorInput.value = hex;
updatePreview();
swatches.forEach(function (s) {
s.style.border = (s.dataset.color === hex)
? '2px solid #0073aa' : '2px solid transparent';
});
}
colorInput.addEventListener('input', function () { updatePreview(); });
nameInput.addEventListener('input', function () { updatePreview(); });
swatches.forEach(function (s) {
s.addEventListener('click', function () { setColor(s.dataset.color); });
});
// Mark active swatch on load
setColor(colorInput.value);
}());
prefix . 'academiccrew_pub_tags' );
$rel_table = esc_sql( $wpdb->prefix . 'academiccrew_pub_tag_relations' );
$tag = $wpdb->get_row( $wpdb->prepare(
"SELECT * FROM {$tags_table} WHERE id = %d", $tag_id
), ARRAY_A );
if ( ! $tag ) {
wp_die( esc_html__( 'Tag not found.', 'academiccrew' ) );
}
$pub_count = (int) $wpdb->get_var( $wpdb->prepare(
"SELECT COUNT(*) FROM {$rel_table} WHERE tag_id = %d", $tag_id
) );
$base_url = admin_url( 'admin.php?page=' . self::MENU_SLUG );
?>
0 ) : ?>
%d publication. Deleting it will remove the tag from that publication.',
'This tag is currently assigned to %d publications. Deleting it will remove the tag from all of them.',
$pub_count,
'academiccrew'
) ),
$pub_count
); ?>
next_color();
if ( ! $name ) {
wp_safe_redirect( add_query_arg( 'acc_msg', 'tag_name_empty',
admin_url( 'admin.php?page=' . self::MENU_SLUG ) ) );
exit;
}
global $wpdb;
$table = esc_sql( $wpdb->prefix . 'academiccrew_pub_tags' );
// Duplicate name check (exclude current tag on edit).
$exists = (int) $wpdb->get_var( $wpdb->prepare(
"SELECT COUNT(*) FROM {$table} WHERE name = %s AND id != %d",
$name, $tag_id
) );
if ( $exists ) {
wp_safe_redirect( add_query_arg( 'acc_msg', 'tag_duplicate',
admin_url( 'admin.php?page=' . self::MENU_SLUG ) ) );
exit;
}
if ( $tag_id ) {
$wpdb->update( $table, [ 'name' => $name, 'color' => $color ], [ 'id' => $tag_id ],
[ '%s', '%s' ], [ '%d' ] );
$msg = 'tag_updated';
} else {
$wpdb->insert( $table, [ 'name' => $name, 'color' => $color ], [ '%s', '%s' ] );
$msg = 'tag_added';
}
wp_safe_redirect( add_query_arg( 'acc_msg', $msg,
admin_url( 'admin.php?page=' . self::MENU_SLUG ) ) );
exit;
}
public function handle_delete(): void {
$tag_id = (int) ( $_POST['tag_id'] ?? 0 );
if ( ! $tag_id
|| ! wp_verify_nonce( sanitize_key( $_POST['_acc_tag_del_nonce'] ?? '' ), 'acc_delete_pub_tag_' . $tag_id )
|| ! current_user_can( 'edit_posts' ) ) {
wp_die( esc_html__( 'Not allowed.', 'academiccrew' ) );
}
global $wpdb;
$wpdb->delete( $wpdb->prefix . 'academiccrew_pub_tag_relations', [ 'tag_id' => $tag_id ], [ '%d' ] );
$wpdb->delete( $wpdb->prefix . 'academiccrew_pub_tags', [ 'id' => $tag_id ], [ '%d' ] );
wp_safe_redirect( add_query_arg( 'acc_msg', 'tag_deleted',
admin_url( 'admin.php?page=' . self::MENU_SLUG ) ) );
exit;
}
// -------------------------------------------------------------------------
// Helpers
// -------------------------------------------------------------------------
/**
* Returns the first palette colour not yet used by any tag.
* Falls back to the first palette entry when all colours are taken.
*/
private function next_color(): string {
global $wpdb;
$used = $wpdb->get_col( "SELECT color FROM {$wpdb->prefix}academiccrew_pub_tags" ) ?: []; // phpcs:ignore
foreach ( self::PALETTE as $hex ) {
if ( ! in_array( $hex, $used, true ) ) {
return $hex;
}
}
return self::PALETTE[0];
}
private function get_notice(): ?array {
$msg = sanitize_key( $_GET['acc_msg'] ?? '' );
return match ( $msg ) {
'tag_added' => [ 'type' => 'success', 'message' => __( 'Tag added.', 'academiccrew' ) ],
'tag_updated' => [ 'type' => 'success', 'message' => __( 'Tag updated.', 'academiccrew' ) ],
'tag_deleted' => [ 'type' => 'success', 'message' => __( 'Tag deleted and removed from all publications.', 'academiccrew' ) ],
'tag_duplicate' => [ 'type' => 'error', 'message' => __( 'A tag with that name already exists.', 'academiccrew' ) ],
'tag_name_empty' => [ 'type' => 'error', 'message' => __( 'Tag name cannot be empty.', 'academiccrew' ) ],
default => null,
};
}
}