prefix . 'patchwork_activity'; } public static function maybe_install(): void { if ( get_option( 'patchwork_core_db_version' ) === self::DB_VERSION ) return; global $wpdb; require_once ABSPATH . 'wp-admin/includes/upgrade.php'; $table = self::table(); $charset = $wpdb->get_charset_collate(); dbDelta( "CREATE TABLE {$table} ( id bigint(20) unsigned NOT NULL AUTO_INCREMENT, created_at datetime NOT NULL, module varchar(40) NOT NULL, action varchar(60) NOT NULL, object_type varchar(40) NOT NULL DEFAULT '', object_id bigint(20) unsigned NOT NULL DEFAULT 0, user_id bigint(20) unsigned NOT NULL DEFAULT 0, message text NOT NULL, data longtext NULL, PRIMARY KEY (id), KEY module_time (module, created_at), KEY object (object_type, object_id) ) {$charset};" ); update_option( 'patchwork_core_db_version', self::DB_VERSION, false ); } public static function log( string $module, string $action, string $message, string $object_type = '', int $object_id = 0, array $data = [] ): void { global $wpdb; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- custom table owned by Patchwork $wpdb->insert( self::table(), [ 'created_at' => current_time( 'mysql', true ), 'module' => substr( sanitize_key( $module ), 0, 40 ), 'action' => substr( sanitize_key( $action ), 0, 60 ), 'object_type' => substr( sanitize_key( $object_type ), 0, 40 ), 'object_id' => $object_id, 'user_id' => get_current_user_id(), 'message' => $message, 'data' => $data ? wp_json_encode( $data ) : null, ], [ '%s', '%s', '%s', '%s', '%d', '%d', '%s', '%s' ] ); } /** * @return array{rows: array, total: int} */ public static function query( string $module = '', int $page = 1, string $object_type = '', int $object_id = 0 ): array { global $wpdb; // Fixed SQL; an empty filter value matches everything. %i (table name) needs WP 6.2+. // phpcs:disable WordPress.DB.DirectDatabaseQuery -- custom table owned by Patchwork; results change on every write $total = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM %i WHERE ( %s = '' OR module = %s ) AND ( %s = '' OR object_type = %s ) AND ( %d = 0 OR object_id = %d )", self::table(), $module, $module, $object_type, $object_type, $object_id, $object_id ) ); $rows = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM %i WHERE ( %s = '' OR module = %s ) AND ( %s = '' OR object_type = %s ) AND ( %d = 0 OR object_id = %d ) ORDER BY id DESC LIMIT %d OFFSET %d", self::table(), $module, $module, $object_type, $object_type, $object_id, $object_id, self::PER_PAGE, max( 0, $page - 1 ) * self::PER_PAGE ) ); // phpcs:enable return [ 'rows' => $rows ?: [], 'total' => $total ]; } /** Number of entries for a module since a UTC datetime (Y-m-d H:i:s). */ public static function count_since( string $module, string $since_utc ): int { global $wpdb; // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- custom table owned by Patchwork return (int) $wpdb->get_var( $wpdb->prepare( 'SELECT COUNT(*) FROM %i WHERE module = %s AND created_at > %s', self::table(), $module, $since_utc ) ); } /** * Table of entries for a module's Activity tab. */ public static function render( string $module ): void { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only pagination $page = max( 1, absint( $_GET['paged'] ?? 1 ) ); $result = self::query( $module, $page ); if ( ! $result['rows'] ) { echo '

' . esc_html__( 'Nothing logged yet.', 'patchwork-order-split' ) . '

'; return; } echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; foreach ( $result['rows'] as $row ) { $when = get_date_from_gmt( $row->created_at, get_option( 'date_format' ) . ' ' . get_option( 'time_format' ) ); $user = $row->user_id ? get_userdata( (int) $row->user_id ) : null; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; } echo '
' . esc_html__( 'When', 'patchwork-order-split' ) . '' . esc_html__( 'What', 'patchwork-order-split' ) . '' . esc_html__( 'On', 'patchwork-order-split' ) . '' . esc_html__( 'Who', 'patchwork-order-split' ) . '
' . esc_html( $when ) . '' . nl2br( esc_html( $row->message ) ) . '' . wp_kses_post( self::object_link( $row->object_type, (int) $row->object_id ) ) . '' . esc_html( $user ? $user->display_name : __( 'System', 'patchwork-order-split' ) ) . '
'; $pages = (int) ceil( $result['total'] / self::PER_PAGE ); if ( $pages > 1 ) { echo '
'; echo wp_kses_post( paginate_links( [ 'base' => add_query_arg( 'paged', '%#%' ), 'format' => '', 'current' => $page, 'total' => $pages, ] ) ); echo '
'; } } private static function object_link( string $type, int $id ): string { if ( ! $id ) return ''; if ( $type === 'order' && function_exists( 'wc_get_order' ) ) { $order = wc_get_order( $id ); return $order ? sprintf( '#%s', esc_url( $order->get_edit_order_url() ), esc_html( $order->get_order_number() ) ) : '#' . $id; } return esc_html( $type . ' #' . $id ); } }