'Item', 'plural' => 'Items', 'ajax' => false, ] ); } public function get_columns() { return [ 'cb' => '', 'name' => __( 'Name', 'adventurebook' ), 'id' => 'ID', 'code' => __( 'Code', 'adventurebook' ), 'stats' => __( 'Stats', 'adventurebook' ), 'image_url' => __( 'Image', 'adventurebook' ), ]; } public function get_sortable_columns() { return [ 'id' => [ 'id', false ], 'code' => [ 'code', false ], 'name' => [ 'name', false ], ]; } public function pagination( $which ) { if ( isset( $_GET['item_slot_filter'] ) && $_GET['item_slot_filter'] !== '' ) { $this->_pagination_args['add_args']['item_slot_filter'] = sanitize_text_field( wp_unslash( $_GET['item_slot_filter'] ) ); } parent::pagination( $which ); } public function column_cb( $item ) { return sprintf( '', intval( $item['id'] ) ); } public function column_default( $item, $column_name ) { switch ( $column_name ) { case 'id': return intval( $item['id'] ); case 'code': return esc_html( $item['code'] ); case 'name': return $this->column_name( $item ); case 'stats': if ( empty( $item['stats'] ) ) { return __( 'No Stats', 'adventurebook' ); } $stats = json_decode( $item['stats'], true ); if ( is_array( $stats ) && ! empty( $stats ) ) { $stats_list = []; foreach ( $stats as $key => $value ) { $stats_list[] = esc_html( $key ) . ': ' . esc_html( $value ); } return implode( ', ', $stats_list ); } return __( 'No Stats', 'adventurebook' ); case 'image_url': return $item['image_url'] ? '' : ''; default: return ''; } } public function column_name( $item ) { $slot_filter = isset( $_GET['item_slot_filter'] ) ? '&item_slot_filter=' . urlencode( sanitize_text_field( wp_unslash( $_GET['item_slot_filter'] ) ) ) : ''; $actions = [ 'edit' => sprintf( '%s', intval( $item['id'] ), $slot_filter, __( 'Edit', 'adventurebook' ) ), 'copy' => sprintf( '%s', intval( $item['id'] ), wp_create_nonce( 'adventurebook_copy_item_' . intval( $item['id'] ) ), $slot_filter, __( 'Copy', 'adventurebook' ) ), 'delete' => sprintf( '%s', intval( $item['id'] ), wp_create_nonce( 'adventurebook_delete_item_' . intval( $item['id'] ) ), $slot_filter, __( 'Really delete?', 'adventurebook' ), __( 'Delete', 'adventurebook' ) ), ]; return sprintf( '%s %s', esc_html( $item['name'] ), $this->row_actions( $actions ) ); } public function get_bulk_actions() { return [ 'bulk-delete' => __( 'Delete', 'adventurebook' ), 'export-json' => __( 'Export as JSON', 'adventurebook' ), ]; } public function process_bulk_action() { global $wpdb; $action = $this->current_action(); if ( 'bulk-delete' === $action ) { if ( ! isset( $_POST['_wpnonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ), 'bulk-items' ) ) { $this->message = __( 'Nonce invalid.', 'adventurebook' ); return; } $item_ids = isset( $_POST['item_ids'] ) ? array_map( 'intval', wp_unslash( $_POST['item_ids'] ) ) : []; if ( $item_ids ) { foreach ( $item_ids as $id ) { $wpdb->delete( "{$wpdb->prefix}adventurebook_item_definitions", [ 'id' => $id ] ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery } $this->message = sprintf( '%d Items deleted.', count( $item_ids ) ); } } // export the selected items as json and output them on the screen if ( 'export-json' === $action ) { if ( ! isset( $_POST['_wpnonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ), 'bulk-items' ) ) { $this->message = __( 'Nonce invalid.', 'adventurebook' ); return; } $item_ids = isset( $_POST['item_ids'] ) ? array_map( 'intval', wp_unslash( $_POST['item_ids'] ) ) : []; if ( $item_ids ) { $items = []; foreach ( $item_ids as $id ) { $item = Item_Definition::get_item_definition_by_id($id); if ( $item ) { $items[] = $item; } } // Output JSON as text on the page (as a message) $this->message =wp_json_encode( $items, JSON_PRETTY_PRINT ); } } } public function prepare_items() { if ( isset( $_REQUEST['s'] ) ) { // bei Suche Nonce prüfen check_admin_referer( 'adventurebook_search_item' ); } global $wpdb; $slot_filter = isset( $_GET['item_slot_filter'] ) ? sanitize_text_field( wp_unslash( $_GET['item_slot_filter'] ) ) : ''; $this->process_bulk_action(); $per_page = 10; $current_page = $this->get_pagenum(); $offset = ( $current_page - 1 ) * $per_page; $allowed_orderby = [ 'id', 'code', 'name' ]; $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' ]; $order = isset( $_GET['order'] ) ? strtoupper( sanitize_text_field( wp_unslash( $_GET['order'] ) ) ) : 'DESC'; if ( ! in_array( $order, $allowed_order, true ) ) { $order = 'DESC'; } $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 ( '' !== $slot_filter ) { // check for "Undefined" filter if ( '__undefined__' === $slot_filter ) { $where .= " AND (JSON_EXTRACT(stats, '$.Slot') IS NULL OR JSON_EXTRACT(stats, '$.Slot') = '')"; // no additional params needed } else { $where .= " AND JSON_EXTRACT(stats, '$.Slot') LIKE %s"; $params[] = '%' . $wpdb->esc_like( $slot_filter ) . '%'; } } $total_items = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->prefix}adventurebook_item_definitions $where", ...$params ) ); $params[] = $per_page; $params[] = $offset; $this->items_data = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}adventurebook_item_definitions $where ORDER BY $orderby $order LIMIT %d OFFSET %d", ...$params ), ARRAY_A ); } else { // Kein Suchbegriff if ( '' !== $slot_filter ) { // if the filter is "Undefined", search for items where Slot is NULL or empty if ( '__undefined__' === $slot_filter ) { $total_items = (int) $wpdb->get_var( " SELECT COUNT(*) FROM {$wpdb->prefix}adventurebook_item_definitions WHERE JSON_EXTRACT(stats, '$.Slot') IS NULL OR JSON_EXTRACT(stats, '$.Slot') = '' " ); $this->items_data = $wpdb->get_results( $wpdb->prepare( " SELECT * FROM {$wpdb->prefix}adventurebook_item_definitions WHERE JSON_EXTRACT(stats, '$.Slot') IS NULL OR JSON_EXTRACT(stats, '$.Slot') = '' ORDER BY $orderby $order LIMIT %d OFFSET %d ", $per_page, $offset ), ARRAY_A ); $this->set_pagination_args( [ 'total_items' => $total_items, 'per_page' => $per_page, 'total_pages' => ceil( $total_items / $per_page ), ] ); $this->_column_headers = [ $this->get_columns(), [], $this->get_sortable_columns() ]; $this->items = $this->items_data; return; } $like_slot = '%' . $wpdb->esc_like( $slot_filter ) . '%'; $total_items = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->prefix}adventurebook_item_definitions WHERE JSON_EXTRACT(stats, '$.Slot') LIKE %s", $like_slot ) ); $this->items_data = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}adventurebook_item_definitions WHERE JSON_EXTRACT(stats, '$.Slot') LIKE %s ORDER BY $orderby $order LIMIT %d OFFSET %d", $like_slot, $per_page, $offset ), ARRAY_A ); } else { // Kein Filter $total_items = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}adventurebook_item_definitions" ); $this->items_data = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}adventurebook_item_definitions ORDER BY $orderby $order LIMIT %d OFFSET %d", $per_page, $offset ), ARRAY_A ); } } $this->set_pagination_args( [ 'total_items' => $total_items, 'per_page' => $per_page, 'total_pages' => ceil( $total_items / $per_page ), ] ); $this->_column_headers = [ $this->get_columns(), [], $this->get_sortable_columns() ]; $this->items = $this->items_data; } public function get_message() { return $this->message; } } /** * ===================================================== * Admin-Post-Handler für Speichern * ===================================================== */ add_action( 'admin_post_adventurebook_save_item', 'adventurebook_handle_save_item' ); function adventurebook_handle_save_item() { if ( ! current_user_can( 'manage_options' ) ) { wp_die( __( 'You do not have permission to do that.', 'adventurebook' ) ); } global $wpdb; // Nonce prüfen $nonce = isset( $_POST['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ) : ''; if ( ! $nonce || ! wp_verify_nonce( $nonce, 'adventurebook_save_item' ) ) { $message = __( 'Invalid nonce.', 'adventurebook' ); $slot_filter = ''; if ( ! empty( $_POST['item_slot_filter'] ) ) { $slot_filter = '&item_slot_filter=' . urlencode( sanitize_text_field( wp_unslash( $_POST['item_slot_filter'] ) ) ); } wp_safe_redirect( admin_url( 'admin.php?page=adventurebook_items' . $slot_filter . '&message=' . urlencode( $message ) ) ); exit; } $id = isset( $_POST['id'] ) ? intval( $_POST['id'] ) : 0; $code = isset( $_POST['code'] ) ? sanitize_text_field( wp_unslash( $_POST['code'] ) ) : ''; $name = isset( $_POST['name'] ) ? sanitize_text_field( wp_unslash( $_POST['name'] ) ) : ''; $description = isset( $_POST['description'] ) ? sanitize_textarea_field( wp_unslash( $_POST['description'] ) ) : ''; // Stats bauen $stats = []; if ( isset( $_POST['item_stats_keys'], $_POST['item_stats_values'] ) ) { $keys = is_array( $_POST['item_stats_keys'] ) ? array_map( 'sanitize_text_field', wp_unslash( $_POST['item_stats_keys'] ) ) : []; $values = is_array( $_POST['item_stats_values'] ) ? array_map( 'sanitize_text_field', wp_unslash( $_POST['item_stats_values'] ) ) : []; for ( $i = 0; $i < count( $keys ); $i++ ) { $key = $keys[ $i ]; $value = $values[ $i ] ?? ''; if ( '' !== $key && '' !== $value ) { $stats[ $key ] = $value; } } } $stats_json = wp_json_encode( $stats ); $image_url = isset( $_POST['image_url'] ) ? esc_url_raw( wp_unslash( $_POST['image_url'] ) ) : ''; if ( $id > 0 ) { $wpdb->update( "{$wpdb->prefix}adventurebook_item_definitions", // phpcs:ignore WordPress.DB.DirectDatabaseQuery compact( 'code', 'name', 'description', 'stats_json', 'image_url' ), [ 'id' => $id ] ); // Achtung: Feldname in DB ist "stats", nicht "stats_json" $wpdb->update( "{$wpdb->prefix}adventurebook_item_definitions", [ 'code' => $code, 'name' => $name, 'description' => $description, 'stats' => $stats_json, 'image_url' => $image_url, ], [ 'id' => $id ] ); $message = __( 'Item updated.', 'adventurebook' ); } else { $wpdb->insert( "{$wpdb->prefix}adventurebook_item_definitions", // phpcs:ignore WordPress.DB.DirectDatabaseQuery [ 'code' => $code, 'name' => $name, 'description' => $description, 'stats' => $stats_json, 'image_url' => $image_url, ] ); $message = __( 'Item created.', 'adventurebook' ); } $slot_filter = ''; if ( ! empty( $_POST['item_slot_filter'] ) ) { $slot_filter = '&item_slot_filter=' . urlencode( sanitize_text_field( wp_unslash( $_POST['item_slot_filter'] ) ) ); } wp_safe_redirect( admin_url( 'admin.php?page=adventurebook_items' . $slot_filter . '&message=' . urlencode( $message ) ) ); exit; } /** * ===================================================== * Admin Page (Liste + Formular) * ===================================================== */ function adventurebook_items_page() { if ( ! current_user_can( 'manage_options' ) ) { return; } global $wpdb; $message = isset( $_GET['message'] ) ? sanitize_text_field( wp_unslash( $_GET['message'] ) ) : ''; $is_list_view = ! isset( $_GET['action'] ) || ! in_array( $_GET['action'], [ 'new', 'edit' ], true ); // copy an item (will duplicate the item definition, set it's code to _copy) if ( isset( $_GET['action'], $_GET['id'] ) && 'copy' === $_GET['action'] ) { $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_copy_item_' . $id ) ) { $item = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}adventurebook_item_definitions WHERE id=%d", $id ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery if ( $item ) { // insert new item with same data, but code appended with "_copy" $new_code = $item->code . '_copy'; $wpdb->insert( "{$wpdb->prefix}adventurebook_item_definitions", // phpcs:ignore WordPress.DB.DirectDatabaseQuery [ 'code' => $new_code, 'name' => $item->name, 'description' => $item->description, 'stats' => $item->stats, 'image_url' => $item->image_url, ] ); $new_id = $wpdb->insert_id; $message = __( 'Item copied.', 'adventurebook' ); // redirect will not work because headers were already sent, so we set $is_list_view to true and show message $is_list_view = true; } } } // Single delete (bleibt per GET hier) if ( isset( $_GET['action'], $_GET['id'] ) && 'delete' === $_GET['action'] ) { $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_item_' . $id ) ) { $wpdb->delete( "{$wpdb->prefix}adventurebook_item_definitions", [ 'id' => $id ] ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery $message = __( 'Item deleted.', 'adventurebook' ); } } echo '
'; if ( $is_list_view ) { // ----------------- LIST VIEW ----------------- $slot_filter = isset( $_GET['item_slot_filter'] ) ? '&item_slot_filter=' . urlencode( sanitize_text_field( wp_unslash( $_GET['item_slot_filter'] ) ) ) : ''; echo '

Adventurebook Items ' . esc_html__( 'New Item', 'adventurebook' ) . '

'; if ( $message ) { echo '

' . esc_html( $message ) . '

'; } $list_table = new Adventurebook_Item_List_Table(); $list_table->prepare_items(); if ( $list_table->get_message() ) { echo '

' . esc_html( $list_table->get_message() ) . '

'; } // SLOT FILTER echo '
'; echo ''; $current_filter = isset( $_GET['item_slot_filter'] ) ? sanitize_text_field( wp_unslash( $_GET['item_slot_filter'] ) ) : ''; $slots = $wpdb->get_col( " SELECT DISTINCT JSON_UNQUOTE(JSON_EXTRACT(stats, '$.Slot')) FROM {$wpdb->prefix}adventurebook_item_definitions WHERE JSON_EXTRACT(stats, '$.Slot') IS NOT NULL AND JSON_EXTRACT(stats, '$.Slot') != '' " ); sort( $slots, SORT_NATURAL | SORT_FLAG_CASE ); echo ''; echo ''; // IMPORT BUTTON // will show a JavaScript Prompt to paste JSON data ?> search_box( esc_html__( 'Search Items', 'adventurebook' ), 'item-search' ); if ( '' !== $current_filter ) { echo ''; } wp_nonce_field( 'adventurebook_search_item' ); echo '
'; echo '
'; $list_table->display(); echo '
'; } else { // ----------------- NEW / EDIT VIEW ----------------- echo '

' . ( ( isset( $_GET['action'] ) && 'edit' === $_GET['action'] ) ? esc_html__( 'Edit Item', 'adventurebook' ) : esc_html__( 'New Item', 'adventurebook' ) ) . '

'; if ( $message ) { echo '

' . esc_html( $message ) . '

'; } $item = null; if ( isset( $_GET['action'], $_GET['id'] ) && 'edit' === $_GET['action'] ) { $id = intval( $_GET['id'] ); $item = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}adventurebook_item_definitions WHERE id=%d", $id ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery } $current_filter = isset( $_GET['item_slot_filter'] ) ? sanitize_text_field( wp_unslash( $_GET['item_slot_filter'] ) ) : ''; ?>
code ) && '' !== $item->code ) : ?>
id ) ) { // neuer Item: vorbefüllte Stats $predefined_stats = [ 'Value' => '0', 'Slot' => '', 'Use' => '', 'UseData' => '', ]; foreach ( $predefined_stats as $key => $value ) : ?>
stats ? json_decode( $item->stats ) : []; foreach ( $stats as $key => $value ) : ?>
code ); adventurebook_show_page_links( $pages ); ?>
get_loottable_item_definitions(); foreach ( $loottable as $loottable_item ) { if ( $loottable_item->code === $item->code ) { return true; } } return false; } ); adventurebook_show_enemy_links( $enemies ); ?>
'adventurebook_page', 'posts_per_page' => -1, 'post_status' => 'publish', ]; $query = new WP_Query( $args ); if ( $query->have_posts() ) { while ( $query->have_posts() ) { $query->the_post(); $shop_items = get_post_meta( get_the_ID(), '_adventurebook_shop_items', true ); if ( ! $shop_items ) { continue; } $shop_items_array = explode( ',', $shop_items ); foreach ( $shop_items_array as $shop_item_code ) { $shop_item = Item_Definition::get_item_definition_by_code( $shop_item_code ); if ( $shop_item && $shop_item->code === $item->code ) { $shop_pages[] = get_post( get_the_ID() ); break; } } } wp_reset_postdata(); } adventurebook_show_page_links( $shop_pages ); ?>
'; } /** * ===================================================== * Admin-Post-Handler für Import * ===================================================== */ add_action( 'admin_post_adventurebook_import_items', 'adventurebook_handle_import_items' ); function adventurebook_handle_import_items() { if ( ! current_user_can( 'manage_options' ) ) { wp_die( __( 'You do not have permission to do that.', 'adventurebook' ) ); } // Nonce prüfen $nonce = isset( $_POST['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ) : ''; if ( ! $nonce || ! wp_verify_nonce( $nonce, 'adventurebook_import_items' ) ) { wp_die( __( 'Invalid nonce.', 'adventurebook' ) ); } global $wpdb; $json_data = isset( $_POST['json_data'] ) ? wp_unslash( $_POST['json_data'] ) : ''; $items = json_decode( $json_data, true ); if ( is_array( $items ) ) { foreach ( $items as $item ) { if ( isset( $item['code'], $item['name'] ) ) { // check if an item with this code does not already exist $item_definition = Item_Definition::get_item_definition_by_code( $item['code'] ); if ( $item_definition ) { continue; // skip existing item } // take the last part of the image_url (filename) and try to get a match from the media library $image_url = ''; if ( isset( $item['image_url'] ) && '' !== $item['image_url'] ) { $image_filename = basename( $item['image_url'] ); $args = [ 'post_type' => 'attachment', 'posts_per_page' => 1, 'post_status' => 'inherit', 'meta_query' => [ [ 'key' => '_wp_attached_file', 'value' => $image_filename, 'compare' => 'LIKE', ], ], ]; $query = new WP_Query( $args ); if ( $query->have_posts() ) { $query->the_post(); $image_url = wp_get_attachment_url( get_the_ID() ); wp_reset_postdata(); } else { $image_url = $item['image_url']; } } $wpdb->insert( "{$wpdb->prefix}adventurebook_item_definitions", // phpcs:ignore WordPress.DB.DirectDatabaseQuery [ 'code' => sanitize_text_field( $item['code'] ), 'name' => sanitize_text_field( $item['name'] ), 'description' => isset( $item['description'] ) ? sanitize_textarea_field( $item['description'] ) : '', 'stats' => isset( $item['stats'] ) ? wp_json_encode( $item['stats'] ) : '', 'image_url' => isset( $item['image_url'] ) ? esc_url_raw( $item['image_url'] ) : '', ] ); } } } // Redirect back to items page wp_safe_redirect( admin_url( 'admin.php?page=adventurebook_items&message=' . urlencode( __( 'Items imported.', 'adventurebook' ) ) ) ); exit; }