$action, 'id' => (int) $id, ) ), 'novoverify_code_' . $action . '_' . (int) $id ); } /** * Stores the "items per page" screen option. * * @param mixed $screen_option Default false. * @param string $option Option name. * @param int $value New value. * @return int */ public function save_per_page( $screen_option, $option, $value ) { return max( 1, min( 999, absint( $value ) ) ); } /** * Runs on load-{hook}: processes actions and prepares the screen. */ public function load() { if ( ! current_user_can( NovoVerify_Config::CAPABILITY ) ) { return; } $this->maybe_save(); $this->maybe_row_action(); $this->maybe_bulk_action(); if ( 'list' === $this->view() ) { add_screen_option( 'per_page', array( 'default' => 20, 'option' => 'novoverify_codes_per_page', ) ); $this->table = new NovoVerify_Codes_List_Table(); } } /** * Which view is requested: list, new or edit. * * @return string */ private function view() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Only selects the view. $action = isset( $_GET['action'] ) ? sanitize_key( wp_unslash( $_GET['action'] ) ) : ''; return in_array( $action, array( 'new', 'edit' ), true ) ? $action : 'list'; } /** * Saves the add/edit form. */ private function maybe_save() { if ( ! isset( $_POST['novoverify_save'] ) ) { return; } check_admin_referer( 'novoverify_save_code' ); $id = isset( $_POST['id'] ) ? absint( $_POST['id'] ) : 0; $raw = isset( $_POST['code'] ) ? sanitize_text_field( wp_unslash( $_POST['code'] ) ) : ''; $product = isset( $_POST['product_name'] ) ? sanitize_text_field( wp_unslash( $_POST['product_name'] ) ) : ''; $batch = isset( $_POST['batch'] ) ? sanitize_text_field( wp_unslash( $_POST['batch'] ) ) : ''; $status = isset( $_POST['status'] ) && NovoVerify_DB::STATUS_REVOKED === $_POST['status'] ? NovoVerify_DB::STATUS_REVOKED : NovoVerify_DB::STATUS_ACTIVE; $args = array( 'product_name' => mb_substr( $product, 0, 191 ), 'batch' => mb_substr( $batch, 0, 100 ), 'status' => $status, ); /** * Filters the data saved from the add/edit code form (the nonce is already verified). * * @param array $args Code data: product_name, batch, status, and optionally meta, product_id. * @param int $id Code ID, 0 for a new code. */ $args = apply_filters( 'novoverify_code_save_args', $args, $id ); $result = $id ? NovoVerify_Codes::update( $id, $raw, $args ) : NovoVerify_Codes::add( $raw, $args ); if ( is_wp_error( $result ) ) { $this->error = $result; return; } /** * Fires after the add/edit code form was saved. * * @param int $id Code ID (the new ID for added codes). * @param array $args Saved data. */ do_action( 'novoverify_code_saved', $id ? $id : (int) $result, $args ); $notice = $id ? 'updated' : 'added'; if ( ! $id && isset( $_POST['novoverify_add_another'] ) ) { wp_safe_redirect( self::url( array( 'action' => 'new', 'novoverify_notice' => $notice, ) ) ); exit; } wp_safe_redirect( self::url( array( 'novoverify_notice' => $notice ) ) ); exit; } /** * Handles single-row links (revoke, reactivate, delete). */ private function maybe_row_action() { if ( ! isset( $_GET['novoverify_action'], $_GET['id'] ) ) { return; } $action = sanitize_key( wp_unslash( $_GET['novoverify_action'] ) ); $id = absint( $_GET['id'] ); if ( ! in_array( $action, array( 'revoke', 'activate', 'delete' ), true ) || ! $id ) { return; } check_admin_referer( 'novoverify_code_' . $action . '_' . $id ); $this->apply( $action, array( $id ) ); } /** * Handles the bulk action dropdown. */ private function maybe_bulk_action() { $action = ''; foreach ( array( 'action', 'action2' ) as $field ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce is checked below once an action is found. $value = isset( $_REQUEST[ $field ] ) ? sanitize_key( wp_unslash( $_REQUEST[ $field ] ) ) : ''; if ( 0 === strpos( $value, 'novoverify_' ) ) { $action = substr( $value, strlen( 'novoverify_' ) ); break; } } if ( ! in_array( $action, array( 'revoke', 'activate', 'delete' ), true ) ) { return; } check_admin_referer( 'bulk-novoverify-codes' ); $ids = isset( $_REQUEST['ids'] ) ? array_map( 'absint', (array) wp_unslash( $_REQUEST['ids'] ) ) : array(); $this->apply( $action, array_filter( $ids ) ); } /** * Applies an action to codes and redirects back with a notice. * * @param string $action revoke, activate or delete. * @param int[] $ids Code IDs. */ private function apply( $action, array $ids ) { if ( empty( $ids ) ) { $this->redirect_back( 'none', 0 ); } switch ( $action ) { case 'revoke': $count = NovoVerify_DB::set_status( $ids, NovoVerify_DB::STATUS_REVOKED ); $notice = 'revoked'; break; case 'activate': $count = NovoVerify_DB::set_status( $ids, NovoVerify_DB::STATUS_ACTIVE ); $notice = 'activated'; break; default: $count = NovoVerify_DB::delete_codes( $ids ); $notice = 'deleted'; } /** * Fires after a bulk or row action on codes. * * @param string $action revoke, activate or delete. * @param int[] $ids Code IDs. */ do_action( 'novoverify_codes_action', $action, $ids ); $this->redirect_back( $notice, $count ); } /** * Redirects to the list, keeping search and filters. * * @param string $notice Notice key. * @param int $count Affected codes. */ private function redirect_back( $notice, $count ) { $keep = array(); foreach ( array( 's', 'status', 'batch', 'paged', 'orderby', 'order' ) as $key ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce verified by the caller. if ( isset( $_REQUEST[ $key ] ) && '' !== $_REQUEST[ $key ] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce verified by the caller. $keep[ $key ] = sanitize_text_field( wp_unslash( $_REQUEST[ $key ] ) ); } } $keep['novoverify_notice'] = $notice; $keep['novoverify_count'] = (int) $count; wp_safe_redirect( self::url( $keep ) ); exit; } /** * Notice text for the current request. * * @return array{type: string, message: string}|null */ private function notice() { // phpcs:disable WordPress.Security.NonceVerification.Recommended -- Display only. $notice = isset( $_GET['novoverify_notice'] ) ? sanitize_key( wp_unslash( $_GET['novoverify_notice'] ) ) : ''; $count = isset( $_GET['novoverify_count'] ) ? absint( $_GET['novoverify_count'] ) : 0; // phpcs:enable WordPress.Security.NonceVerification.Recommended $formatted = number_format_i18n( $count ); switch ( $notice ) { case 'added': return array( 'type' => 'success', 'message' => __( 'Code added.', 'novoverify' ), ); case 'updated': return array( 'type' => 'success', 'message' => __( 'Code updated.', 'novoverify' ), ); case 'revoked': return array( 'type' => 'success', /* translators: %s: number of codes. */ 'message' => sprintf( _n( '%s code revoked.', '%s codes revoked.', $count, 'novoverify' ), $formatted ), ); case 'activated': return array( 'type' => 'success', /* translators: %s: number of codes. */ 'message' => sprintf( _n( '%s code reactivated.', '%s codes reactivated.', $count, 'novoverify' ), $formatted ), ); case 'deleted': return array( 'type' => 'success', /* translators: %s: number of codes. */ 'message' => sprintf( _n( '%s code deleted.', '%s codes deleted.', $count, 'novoverify' ), $formatted ), ); case 'none': return array( 'type' => 'warning', 'message' => __( 'No codes were selected.', 'novoverify' ), ); } return null; } /** * Renders the screen. */ public function render() { if ( ! current_user_can( NovoVerify_Config::CAPABILITY ) ) { wp_die( esc_html__( 'You do not have permission to access this page.', 'novoverify' ) ); } $notice = $this->notice(); $view = $this->view(); if ( 'list' === $view && $this->table ) { $table = $this->table; $table->prepare_items(); include NOVOVERIFY_PATH . 'admin/views/codes.php'; return; } $code = null; if ( 'edit' === $view ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only. $code = NovoVerify_DB::get_code( isset( $_GET['id'] ) ? absint( $_GET['id'] ) : 0 ); if ( ! $code ) { wp_die( esc_html__( 'The code was not found. It may have been deleted.', 'novoverify' ) ); } } // Keep what the user typed when saving failed. $values = array( 'code' => $code ? $code->display_code : '', 'product_name' => $code ? $code->product_name : '', 'batch' => $code ? $code->batch : '', 'status' => $code ? $code->status : NovoVerify_DB::STATUS_ACTIVE, ); if ( $this->error ) { // phpcs:disable WordPress.Security.NonceVerification.Missing -- Nonce verified in maybe_save(). foreach ( array_keys( $values ) as $key ) { if ( isset( $_POST[ $key ] ) ) { $values[ $key ] = sanitize_text_field( wp_unslash( $_POST[ $key ] ) ); } } // phpcs:enable WordPress.Security.NonceVerification.Missing } $error = $this->error; include NOVOVERIFY_PATH . 'admin/views/code-edit.php'; } }