0 ) {
$wpdb->update( "{$wpdb->prefix}adventurebook_enemy_definitions",// phpcs:ignore WordPress.DB.DirectDatabaseQuery
compact( 'code', 'name', 'description', 'is_hostile', 'can_be_attacked', 'xp_value', 'stats', 'inventory', 'image_url' ),
[ 'id' => $id ]
);
$message = __( 'Enemy updated.', 'adventurebook' );
} else {
$wpdb->insert( "{$wpdb->prefix}adventurebook_enemy_definitions",// phpcs:ignore WordPress.DB.DirectDatabaseQuery
compact( 'code', 'name', 'description', 'is_hostile', 'can_be_attacked', 'xp_value', 'stats', 'inventory', 'image_url' )
);
$message = __( 'Enemy created.', 'adventurebook' );
}
} else {
$message = __( 'Invalid nonce.', 'adventurebook' );
}
}
$type_filter = isset( $_POST['enemy_type_filter'] )
? '&enemy_type_filter=' . urlencode( sanitize_text_field( $_POST['enemy_type_filter'] ) )
: '';
wp_safe_redirect(
admin_url( 'admin.php?page=adventurebook-enemies' . $type_filter . '&message=' . urlencode( $message ) )
);
exit;
}
if ( ! class_exists( 'WP_List_Table' ) ) {
require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php';
}
class Adventurebook_Enemy_List_Table extends WP_List_Table {
private array $enemies_data = [];
private string $message = '';
function __construct() {
parent::__construct( [
'singular' => 'Enemy',
'plural' => 'Enemies',
'ajax' => false
] );
}
function pagination($which) {
if (isset($_GET['enemy_type_filter']) && $_GET['enemy_type_filter'] !== '') {
$this->_pagination_args['add_args']['enemy_type_filter'] = sanitize_text_field($_GET['enemy_type_filter']);
}
parent::pagination($which);
}
function get_columns(): array {
return [
'cb' => '',
'name' => __( 'Name', 'adventurebook' ),
'lvl' => __( 'Might', 'adventurebook' ),
'id' => 'ID',
'code' => __( 'Code', 'adventurebook' ),
'type' => __( 'Type', 'adventurebook' ),
'stats' => __( 'Stats', 'adventurebook' ),
'inventory' => __( 'Loottable', 'adventurebook' ),
'image_url' => __( 'Image', 'adventurebook' ),
];
}
function get_sortable_columns(): array {
return [
'id' => [ 'id', false ],
'lvl' => [ 'lvl', false ],
'code' => [ 'code', false ],
'type' => [ 'type', false ],
'name' => [ 'name', false ],
];
}
function column_lvl( $item ) {
/** @var Enemy_Definition $item */
return $item->get_combat_strength();
}
function column_type( $item ) {
/** @var Enemy_Definition $item */
return isset( $item->stats['Type'] ) ? esc_html( $item->stats['Type'] ) : '';
}
function column_cb( $item ) {
/** @var Enemy_Definition $item */
return sprintf( '', $item->id );
}
function column_default( $item, $column_name ) {
/** @var Enemy_Definition $item */
switch ( $column_name ) {
case 'id':
return intval( $item->id );
case 'code':
return esc_html( $item->code );
case 'lvl':
return $this->column_lvl( $item );
case 'type':
return $this->column_type( $item );
case 'name':
return $this->column_name( $item );
case 'stats':
// Stats als lesbare Liste anzeigen
if ( empty( $item->stats ) ) {
return __( 'No stats', 'adventurebook' );
}
$stats = $item->stats;
if ( is_array( $stats ) && ! empty( $stats ) ) {
$stats_list = [];
foreach ( $stats as $key => $value ) {
$stats_list[] = esc_html( $key ) . ': ' . esc_html( $value );
}
// return as list embedded in "
" and ""
return '' . implode( '', $stats_list ) . '';
} else {
return __( 'No stats', 'adventurebook' );
}
case 'inventory':
// the inventory (= loot table) is a comma separated list of code:chance:min:max
if ( empty( $item->inventory ) ) {
return __( 'No loottable', 'adventurebook' );
}
return $item->getInventoryAsHTML();
case 'image_url':
return $item->image_url ? '
' : '';
default:
return '';
}
}
function column_name( Enemy_Definition $enemy ): string {
$type_filter = isset($_GET['enemy_type_filter']) ? '&enemy_type_filter=' . urlencode(sanitize_text_field($_GET['enemy_type_filter'])) : '';
$actions = [
'edit' => sprintf(
'%s',
$enemy->id,
$type_filter,
__('Edit', 'adventurebook')
),
'copy' => sprintf(
'%s',
$enemy->id,
wp_create_nonce('adventurebook_copy_enemy_' . $enemy->id),
$type_filter,
__('Copy', 'adventurebook')
),
'delete' => sprintf(
'%s',
$enemy->id,
wp_create_nonce('adventurebook_delete_enemy_' . $enemy->id),
$type_filter,
__('Really delete?', 'adventurebook'),
__('Delete', 'adventurebook')
),
];
return sprintf( '%s %s', esc_html( $enemy->name ), $this->row_actions( $actions ) );
}
function get_bulk_actions(): array {
return [ 'bulk-delete' => __( 'Delete', 'adventurebook' ) ];
}
function process_bulk_action() {
// check for admin permissions
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
global $wpdb;
$action = $this->current_action();
if ( $action === 'bulk-delete' ) {
// WP-Standard Bulk-Nonce for Enemies
if ( ! isset( $_POST['_wpnonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ), 'bulk-enemies' ) ) {
$this->message = __( 'Nonce invalid.', 'adventurebook' );
return;
}
$enemy_ids = isset( $_POST['enemy_ids'] ) ? array_map( 'intval', wp_unslash( $_POST['enemy_ids'] ) ) : [];
if ( $enemy_ids ) {
foreach ( $enemy_ids as $id ) {
Enemy_Definition::delete_enemy_definition( $id );
}
$this->message = sprintf( '%d Enemies deleted.', count( $enemy_ids ) );
}
}
}
function prepare_enemies() {
if ( isset ( $_REQUEST['s'] )
) {
// did we get ANY search request? If yes, check the nonce
check_admin_referer( 'adventurebook_search_item' );
}
global $wpdb;
$enemy_type_filter = isset($_GET['enemy_type_filter']) ? sanitize_text_field($_GET['enemy_type_filter']) : '';
$this->process_bulk_action();
$per_page = 10;
$current_page = $this->get_pagenum();
$offset = ( $current_page - 1 ) * $per_page;
$allowed_orderby = [ 'id', 'code', 'name' ];
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$orderby = isset( $_GET['orderby'] ) ? sanitize_text_field( wp_unslash( $_GET['orderby'] ) ) : 'id';
if ( ! in_array( $orderby, $allowed_orderby, true ) ) {
$orderby = 'id';
}
$allowed_order = [ 'ASC', 'DESC' ];
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$order = isset( $_GET['order'] ) ? strtoupper( sanitize_text_field( wp_unslash( $_GET['order'] ) ) ) : 'DESC';
if ( ! in_array( $order, $allowed_order, true ) ) {
$order = 'DESC';
}
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$search = isset( $_REQUEST['s'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['s'] ) ) : '';
if ($search !== '') {
// Suchmodus
$like = '%' . $wpdb->esc_like($search) . '%';
$where = "WHERE (code LIKE %s OR name LIKE %s)";
$params = [$like, $like];
if ($enemy_type_filter !== '') {
// check for filter "Undefined": we need to find all entries where the Type is either NULL or an empty string
if ($enemy_type_filter === '__undefined__') {
$where .= " AND (JSON_EXTRACT(stats, '$.Type') IS NULL OR JSON_EXTRACT(stats, '$.Type') = '')";
} else {
$where .= " AND JSON_EXTRACT(stats, '$.Type') LIKE %s";
$params[] = '%' . $wpdb->esc_like($enemy_type_filter) . '%';
}
}
$total_enemies = (int) $wpdb->get_var(
$wpdb->prepare("SELECT COUNT(*) FROM {$wpdb->prefix}adventurebook_enemy_definitions $where", ...$params)
);
$params[] = $per_page;
$params[] = $offset;
$raw_data = $wpdb->get_results(
$wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}adventurebook_enemy_definitions $where ORDER BY $orderby $order LIMIT %d OFFSET %d",
...$params
),
ARRAY_A
);
} else {
// Kein Suchbegriff
if ($enemy_type_filter !== '') {
// check for filter "Undefined": we need to find all entries where the Type is either NULL or an empty string
if( $enemy_type_filter === '__undefined__' ) {
$total_enemies = (int) $wpdb->get_var(
"SELECT COUNT(*) FROM {$wpdb->prefix}adventurebook_enemy_definitions WHERE JSON_EXTRACT(stats, '$.Type') IS NULL OR JSON_EXTRACT(stats, '$.Type') = ''"
);
$raw_data = $wpdb->get_results(
$wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}adventurebook_enemy_definitions
WHERE JSON_EXTRACT(stats, '$.Type') IS NULL OR JSON_EXTRACT(stats, '$.Type') = ''
ORDER BY $orderby $order
LIMIT %d OFFSET %d",
$per_page, $offset
),
ARRAY_A
);
$this->set_pagination_args( [
'total_items' => $total_enemies,
'per_page' => $per_page,
'total_pages' => ceil( $total_enemies / $per_page ),
] );
$this->_column_headers = [ $this->get_columns(), [], $this->get_sortable_columns() ];
$this->items = $this->enemies_data;
// jetzt: Enemy_Definition Objekte bauen
$this->enemies_data = [];
foreach ( $raw_data as $row ) {
$this->enemies_data[] = Enemy_Definition::get_enemy_definition_by_id( $row['id'] );
}
$this->set_pagination_args( [
'total_items' => $total_enemies,
'per_page' => $per_page,
'total_pages' => ceil( $total_enemies / $per_page ),
] );
$this->_column_headers = [ $this->get_columns(), [], $this->get_sortable_columns() ];
$this->items = $this->enemies_data;
return;
}
$like_type = '%' . $wpdb->esc_like($enemy_type_filter) . '%';
$total_enemies = (int) $wpdb->get_var(
$wpdb->prepare(
"SELECT COUNT(*) FROM {$wpdb->prefix}adventurebook_enemy_definitions WHERE JSON_EXTRACT(stats, '$.Type') LIKE %s",
$like_type
)
);
$raw_data = $wpdb->get_results(
$wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}adventurebook_enemy_definitions
WHERE JSON_EXTRACT(stats, '$.Type') LIKE %s
ORDER BY $orderby $order
LIMIT %d OFFSET %d",
$like_type, $per_page, $offset
),
ARRAY_A
);
} else {
// Kein Filter
$total_enemies = (int) $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}adventurebook_enemy_definitions");
$raw_data = $wpdb->get_results(
$wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}adventurebook_enemy_definitions ORDER BY $orderby $order LIMIT %d OFFSET %d",
$per_page, $offset
),
ARRAY_A
);
}
}
// now convert all $raw_data entries to Enemy_Definition objects
$this->enemies_data = [];
foreach ( $raw_data as $row ) {
$this->enemies_data[] = Enemy_Definition::get_enemy_definition_by_id( $row['id'] );
}
if ( ab_get( 'orderby' ) === 'lvl' ) {
usort( $this->enemies_data, function ( $a, $b ) use ( $order ) {
$a_lvl = $a->get_combat_strength();
$b_lvl = $b->get_combat_strength();
if ( ab_get( 'order' ) === 'asc' ) {
return $a_lvl <=> $b_lvl;
} else {
return $b_lvl <=> $a_lvl;
}
} );
}
$this->set_pagination_args( [
'total_items' => $total_enemies,
'per_page' => $per_page,
'total_pages' => ceil( $total_enemies / $per_page ),
] );
$this->_column_headers = [ $this->get_columns(), [], $this->get_sortable_columns() ];
$this->items = $this->enemies_data;
}
public function get_message() {
return $this->message;
}
}
// Admin page
function adventurebook_enemies_page() {
// check for admin permissions
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
global $wpdb;
// single delete
if ( isset( $_GET['action'], $_GET['id'] ) && $_GET['action'] === 'delete' ) {
// check nonce
$id = intval( sanitize_text_field( wp_unslash( $_GET['id'] ) ) );
$nonce = isset( $_GET['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ) : '';
if ( $nonce && wp_verify_nonce( $nonce, 'adventurebook_delete_enemy_' . $id ) ) {
$wpdb->delete( "{$wpdb->prefix}adventurebook_enemy_definitions", [ 'id' => $id ] );// phpcs:ignore WordPress.DB.DirectDatabaseQuery
$message = __( 'Enemy deleted.', 'adventurebook' );
}
}
// copy the enemy definition and set the code to _copy
if ( isset( $_GET['action'], $_GET['enemy_id'] ) && $_GET['action'] === 'copy' ) {
// check nonce
$copy_id = intval( ab_get( 'enemy_id' ) );
$nonce = isset( $_GET['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ) : '';
do_log('Copying enemy with id ' . $copy_id);
if ( $nonce && wp_verify_nonce( $nonce, 'adventurebook_copy_enemy_' . $copy_id ) ) {
do_log('Nonce verified');
$enemy_to_copy = Enemy_Definition::get_enemy_definition_by_id( $copy_id );
if ( $enemy_to_copy ) {
$new_code = $enemy_to_copy->code . '_copy';
// ensure unique code
$existing_enemy = $wpdb->get_row( $wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}adventurebook_enemy_definitions WHERE code = %s",
$new_code
), ARRAY_A );
if ( $existing_enemy ) {
$new_code .= '_' . time();
}
$wpdb->insert( "{$wpdb->prefix}adventurebook_enemy_definitions",// phpcs:ignore WordPress.DB.DirectDatabaseQuery
[
'code' => $new_code,
'name' => $enemy_to_copy->name . ' (Copy)',
'description' => $enemy_to_copy->description,
'is_hostile' => $enemy_to_copy->is_hostile,
'can_be_attacked' => $enemy_to_copy->can_be_attacked,
'xp_value' => $enemy_to_copy->xp_value,
'stats' => json_encode( $enemy_to_copy->stats ),
'inventory' => json_encode( $enemy_to_copy->inventory ),
'image_url' => $enemy_to_copy->image_url,
]
);
$message = __( 'Enemy copied.', 'adventurebook' );
} else {
$message = __( 'Enemy to copy not found.', 'adventurebook' );
}
}
}
$message = '';
$is_list_view = ! isset( $_GET['action'] ) || ! in_array( $_GET['action'], [ 'new', 'edit' ], true );
echo '';
if ( $is_list_view ) {
$type_filter = isset($_GET['type_filter']) ? '&type_filter=' . urlencode(sanitize_text_field($_GET['type_filter'])) : '';
echo '
';
} else {
echo '
' . ( ( isset( $_GET['action'] ) && $_GET['action'] === 'edit' ) ? 'Edit Enemy' : 'New Enemy' ) . '
';
}
if ( $message ) {
echo '
' . esc_html( $message ) . '
';
if ( ! $is_list_view ) {
$type_filter = isset($_GET['type_filter']) ? '&type_filter=' . urlencode(sanitize_text_field($_GET['type_filter'])) : '';
echo '
Back to Enemy-List
';
}
echo '
';
}
// form new/edit
if ( ! $is_list_view ) {
/** @var Enemy_Definition $enemy */
$enemy = null;
if ( isset( $_GET['action'], $_GET['id'] ) && $_GET['action'] === 'edit' ) {
$id = intval( $_GET['id'] );
$enemy = Enemy_Definition::get_enemy_definition_by_id( $id );
} else {
$enemy = new Enemy_Definition( 0, '', '', 0, 0, 0, '', '{}', '{}', '' );
}
?>
prepare_enemies();
if ( $list_table->get_message() ) {
echo '
' . esc_html( $list_table->get_message() ) . '
';
}
echo '
';
// --- Ende Filter-Dropdown ---
wp_nonce_field( 'adventurebook_search_item' );
echo '';
echo '
';
}
echo '
';
}