$value ) { $sanitized[] = array( 'key' => $key, 'value' => $value, ); } return $sanitized; } /** * A `meta_data` array with every audit key removed — used by the v1 update * path and both v2 forwards. The audit trail is write-once at the sale: an * edit under a different cashier/store (or a forged payload) must not * rewrite it. (The one sanctioned exception is v2's explicit reassignment * flow in Write_Controller, which re-stamps `_pos_user`/`_pos_store` with * its own authorization checks and an order note.) * * WooCommerce resolves an entry by its `id` BEFORE its `key` and overwrites * both the row's key and value — so an id-addressed entry under a harmless * key would rename an audit row away. Pass the order's audit-row ids as * `$protected_meta_ids` and such entries are dropped too. * * @param array $meta_data REST `meta_data` entries (arrays or objects with key/value). * @param int[] $protected_meta_ids Existing audit-row meta ids on the target order (see audit_meta_ids()). * * @return array */ public static function strip_audit_meta( array $meta_data, array $protected_meta_ids = array() ): array { $strip = self::audit_meta_keys(); return array_values( array_filter( $meta_data, static function ( $entry ) use ( $strip, $protected_meta_ids ) { if ( \in_array( self::entry_key( $entry ), $strip, true ) ) { return false; } $id = \is_array( $entry ) ? ( $entry['id'] ?? null ) : ( \is_object( $entry ) ? ( $entry->id ?? null ) : null ); return ! ( is_numeric( $id ) && \in_array( (int) $id, $protected_meta_ids, true ) ); } ) ); } /** * Meta ids of the order's existing audit rows — the rows an id-addressed * `meta_data` entry could target (see strip_audit_meta()). * * @param mixed $order A WC_Order (or false/null when lookup failed — safe no-op). * * @return int[] */ public static function audit_meta_ids( $order ): array { if ( ! \is_object( $order ) || ! method_exists( $order, 'get_meta_data' ) ) { return array(); } $keys = self::audit_meta_keys(); $ids = array(); foreach ( $order->get_meta_data() as $meta ) { $data = \is_object( $meta ) && method_exists( $meta, 'get_data' ) ? $meta->get_data() : array(); if ( isset( $data['id'], $data['key'] ) && \in_array( (string) $data['key'], $keys, true ) ) { $ids[] = (int) $data['id']; } } return $ids; } /** * The validated till key⇒value map from a client `meta_data` array (last * entry wins for a repeated key, invalid values dropped). This is the one * parser for till values — callers must not rebuild the extraction loop. * * @param array $meta_data REST `meta_data` entries (arrays or objects with key/value). * * @return array */ public static function till_meta_from_payload( array $meta_data ): array { $client = array(); foreach ( $meta_data as $entry ) { $key = self::entry_key( $entry ); if ( null !== $key ) { $client[ $key ] = self::entry_value( $entry ); } } $till = array(); foreach ( self::TILL_META_KEYS as $key ) { if ( array_key_exists( $key, $client ) && self::is_valid_till_value( $key, $client[ $key ] ) ) { $till[ $key ] = (string) $client[ $key ]; } } return $till; } /** * Whether a till value may persist: never an empty/non-scalar value, and the * cash AMOUNTS must be unsigned plain decimals (a malformed amount would break * Pro analytics aggregations). `_pos_store` is an identifier — the store-scope * model allows numeric ids, uuids, or slugs — so any non-empty scalar is kept. * * @param string $key The till meta key. * @param mixed $value The client-supplied value. * * @return bool */ public static function is_valid_till_value( string $key, $value ): bool { if ( ! \is_scalar( $value ) || '' === (string) $value ) { return false; } if ( \in_array( $key, self::CASH_META_KEYS, true ) && 1 !== preg_match( '/^\d+(?:\.\d+)?$/', (string) $value ) ) { return false; } return true; } /** * The entry's meta key, or null for a malformed entry. A `key` that is an * array/object must not be used for comparisons (PHP "Illegal offset type" * territory) — treat it as unrecognized rather than crash the write. * * @param mixed $entry A REST `meta_data` entry. * * @return string|null */ private static function entry_key( $entry ) { $key = Meta_Entry::key( $entry ); return \is_scalar( $key ) ? (string) $key : null; } /** * The entry's value ('' when absent or malformed). * * @param mixed $entry A REST `meta_data` entry. * * @return mixed */ private static function entry_value( $entry ) { return Meta_Entry::value( $entry ) ?? ''; } }