%d', $pending ); } add_menu_page( __( 'Tillkeeper', 'tillkeeper' ), $label, 'manage_woocommerce', 'tillkeeper', 'tlkp_render_admin_page', 'dashicons-shield', 56 ); // Same page, but the first submenu item gets a plain label instead of the count. add_submenu_page( 'tillkeeper', __( 'Tillkeeper', 'tillkeeper' ), __( 'Overview', 'tillkeeper' ), 'manage_woocommerce', 'tillkeeper', 'tlkp_render_admin_page' ); } add_action( 'admin_menu', 'tlkp_register_admin_page' ); /** * @param string $hook Admin page hook. */ function tlkp_admin_assets( $hook ) { if ( 'toplevel_page_tillkeeper' !== $hook ) { return; } wp_enqueue_style( 'tlkp-admin', TLKP_PLUGIN_URL . 'assets/css/admin.css', array(), TLKP_VERSION ); wp_enqueue_script( 'tlkp-admin', TLKP_PLUGIN_URL . 'assets/js/admin.js', array(), TLKP_VERSION, array( 'in_footer' => true ) ); } add_action( 'admin_enqueue_scripts', 'tlkp_admin_assets' ); /** * Saves settings and clears the log (both nonce-checked form posts). */ function tlkp_handle_admin_forms() { if ( ! current_user_can( 'manage_woocommerce' ) ) { wp_die( esc_html__( 'You do not have permission to do this.', 'tillkeeper' ) ); } check_admin_referer( 'tlkp_admin_form' ); $form = isset( $_POST['tlkp_form'] ) ? sanitize_key( wp_unslash( $_POST['tlkp_form'] ) ) : ''; if ( 'settings' === $form ) { tlkp_update_settings( array( 'guard_product_delete' => ! empty( $_POST['guard_product_delete'] ), 'order_status_mode' => isset( $_POST['order_status_mode'] ) ? sanitize_key( wp_unslash( $_POST['order_status_mode'] ) ) : '', ) ); $notice = 'settings_saved'; } else { tlkp_activity_log_clear(); $notice = 'log_cleared'; } wp_safe_redirect( add_query_arg( 'tlkp_notice', $notice, admin_url( 'admin.php?page=tillkeeper' ) ) ); exit; } add_action( 'admin_post_tlkp_admin_form', 'tlkp_handle_admin_forms' ); /** * Notice after a redirect. The actions that set it checked their own nonce. */ function tlkp_render_notice() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- display-only flag set after a nonce-checked redirect. $key = isset( $_GET['tlkp_notice'] ) ? sanitize_key( wp_unslash( $_GET['tlkp_notice'] ) ) : ''; $messages = array( 'approve_ok' => array( 'success', __( 'Approved and applied.', 'tillkeeper' ) ), 'approve_failed' => array( 'error', __( 'Could not run that request. See the activity log for the error.', 'tillkeeper' ) ), 'reject_ok' => array( 'success', __( 'Rejected. Nothing was changed.', 'tillkeeper' ) ), 'reject_failed' => array( 'error', __( 'Could not reject that request.', 'tillkeeper' ) ), 'settings_saved' => array( 'success', __( 'Settings saved.', 'tillkeeper' ) ), 'log_cleared' => array( 'success', __( 'Activity log cleared.', 'tillkeeper' ) ), ); if ( isset( $messages[ $key ] ) ) { printf( '

%2$s

', esc_attr( $messages[ $key ][0] ), esc_html( $messages[ $key ][1] ) ); } } /** * @param int $user_id User ID. * @return string Login or a placeholder. */ function tlkp_user_label( $user_id ) { $user = $user_id ? get_userdata( (int) $user_id ) : false; return $user ? $user->user_login : __( '(unknown)', 'tillkeeper' ); } /** * @param string $type Object type. * @param int $id Object ID. * @return string Plain text. */ function tlkp_object_label( $type, $id ) { if ( ! $id ) { return '' === $type ? '—' : $type; } if ( 'product' === $type && function_exists( 'wc_get_product' ) ) { $product = wc_get_product( $id ); if ( $product ) { return sprintf( '%s #%d', $product->get_name(), $id ); } } /* translators: 1: object type, 2: object ID. */ return sprintf( __( '%1$s #%2$d', 'tillkeeper' ), $type, $id ); } /** * Renders the page. */ function tlkp_render_admin_page() { if ( ! current_user_can( 'manage_woocommerce' ) ) { return; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only filter. $kind = isset( $_GET['kind'] ) && 'write' === $_GET['kind'] ? 'write' : ''; $pending = tlkp_approval_queue_get_pending(); $resolved = tlkp_approval_queue_get_resolved( 10 ); $settings = tlkp_get_settings(); $log = tlkp_activity_log_get( 100, $kind ); ?>

|

__( 'Done', 'tillkeeper' ), 'applied' => __( 'Applied', 'tillkeeper' ), 'pending_approval' => __( 'Held for approval', 'tillkeeper' ), 'preview' => __( 'Preview only', 'tillkeeper' ), 'approved' => __( 'Approved and applied', 'tillkeeper' ), 'rejected' => __( 'Rejected', 'tillkeeper' ), 'error' => __( 'Failed', 'tillkeeper' ), ); return isset( $labels[ $outcome ] ) ? $labels[ $outcome ] : $outcome; } /** * A short, PII-free summary of what a queued call would do. * * @param array $entry Queue entry. * @return string Leading space + summary, or empty. */ function tlkp_describe_input( array $entry ) { $input = isset( $entry['input'] ) && is_array( $entry['input'] ) ? $entry['input'] : array(); if ( 'woocommerce/order-update-status' === $entry['ability'] && isset( $input['status'] ) ) { /* translators: %s: order status. */ return ' ' . sprintf( __( '→ %s', 'tillkeeper' ), sanitize_key( $input['status'] ) ); } if ( 'woocommerce/product-delete' === $entry['ability'] ) { return ! empty( $input['force'] ) ? ' ' . __( '(permanently)', 'tillkeeper' ) : ' ' . __( '(to trash)', 'tillkeeper' ); } return ''; } /** * @return array[] One row per WooCommerce / Tillkeeper ability. */ function tlkp_store_abilities_overview() { if ( ! function_exists( 'wp_get_abilities' ) ) { return array(); } $guarded = tlkp_guarded_abilities(); $rows = array(); foreach ( wp_get_abilities() as $ability ) { $name = $ability->get_name(); if ( ! tlkp_is_logged_ability( $name ) ) { continue; } $annotations = (array) $ability->get_meta_item( 'annotations', array() ); $read = ! empty( $annotations['readonly'] ); $destructive = ! empty( $annotations['destructive'] ); if ( $read ) { $type = __( 'Read', 'tillkeeper' ); } elseif ( $destructive ) { $type = __( 'Change (destructive)', 'tillkeeper' ); } else { $type = __( 'Change', 'tillkeeper' ); } if ( isset( $guarded[ $name ] ) ) { $level = 'guarded'; $protection = __( 'Guarded', 'tillkeeper' ); } elseif ( $read ) { $level = 'logged'; $protection = __( 'Logged', 'tillkeeper' ); } else { $level = $destructive ? 'open' : 'logged'; $protection = $destructive ? __( 'Logged only — runs immediately', 'tillkeeper' ) : __( 'Logged', 'tillkeeper' ); } $rows[] = array( 'name' => $name, 'label' => $ability->get_label(), 'type' => $type, 'level' => $level, 'protection' => $protection, ); } usort( $rows, static function ( $a, $b ) { return strcmp( $a['name'], $b['name'] ); } ); return $rows; }