'text', 'form' => 'int', 'entry_status' => 'key', 'date_from' => 'date', 'date_to' => 'date', // A key, not an integer: the guest filter travels as the word "guest", // because its numeric value is 0 and 0 already means "no filter". 'user' => 'key', 'orderby' => 'key', 'order' => 'key', ); /** * Returns every filter a user has saved. * * @param int $user_id Optional. User to read. Defaults to the current one. * * @return array}> Filters keyed by slug. */ public function all( int $user_id = 0 ): array { $user_id = $this->user( $user_id ); if ( 0 === $user_id ) { return array(); } $stored = get_user_meta( $user_id, self::META_KEY, true ); $stored = is_array( $stored ) ? $stored : array(); $filters = array(); foreach ( $stored as $key => $filter ) { $key = sanitize_key( (string) $key ); $name = is_array( $filter ) ? (string) ( $filter['name'] ?? '' ) : ''; if ( '' === $key || '' === $name ) { continue; } $filters[ $key ] = array( 'name' => $name, 'args' => $this->clean( is_array( $filter['args'] ?? null ) ? $filter['args'] : array() ), ); } return $filters; } /** * Returns one saved filter. * * @param string $key Filter slug. * @param int $user_id Optional. User to read. Defaults to the current one. * * @return array{name: string, args: array}|null The filter, or null. */ public function find( string $key, int $user_id = 0 ): ?array { return $this->all( $user_id )[ sanitize_key( $key ) ] ?? null; } /** * Stores a filter under a name. * * Saving under a name that already exists replaces it, which is what someone * correcting a filter expects rather than a second entry with the same label. * * @param string $name Name shown in the list. * @param array $args Filter arguments. * @param int $user_id Optional. User to write. Defaults to the current one. * * @return string The slug it was stored under, or an empty string when it was refused. */ public function save( string $name, array $args, int $user_id = 0 ): string { $user_id = $this->user( $user_id ); $name = trim( sanitize_text_field( $name ) ); if ( 0 === $user_id || '' === $name ) { return ''; } $args = $this->clean( $args ); if ( array() === $args ) { return ''; } $filters = $this->all( $user_id ); $key = sanitize_key( sanitize_title( $name ) ); $key = '' === $key ? 'filter-' . count( $filters ) : $key; if ( ! isset( $filters[ $key ] ) && count( $filters ) >= self::LIMIT ) { return ''; } $filters[ $key ] = array( 'name' => $name, 'args' => $args, ); update_user_meta( $user_id, self::META_KEY, $filters ); return $key; } /** * Removes a saved filter. * * @param string $key Filter slug. * @param int $user_id Optional. User to write. Defaults to the current one. * * @return bool True when a filter was removed. */ public function delete( string $key, int $user_id = 0 ): bool { $user_id = $this->user( $user_id ); $key = sanitize_key( $key ); $filters = $this->all( $user_id ); if ( 0 === $user_id || ! isset( $filters[ $key ] ) ) { return false; } unset( $filters[ $key ] ); update_user_meta( $user_id, self::META_KEY, $filters ); return true; } /** * Keeps only the known arguments, cleaned to their type. * * @param array $args Candidate arguments. * * @return array The arguments worth storing. */ private function clean( array $args ): array { $clean = array(); foreach ( self::FIELDS as $field => $type ) { if ( ! isset( $args[ $field ] ) || ! is_scalar( $args[ $field ] ) ) { continue; } $value = (string) $args[ $field ]; $value = match ( $type ) { 'int' => (int) $value, 'key' => sanitize_key( $value ), 'date' => 1 === preg_match( '/^\d{4}-\d{2}-\d{2}$/', $value ) ? $value : '', default => sanitize_text_field( $value ), }; // A filter is defined by what it narrows, so empty values are dropped // rather than stored as "no filter" and rebuilt into the URL later. if ( '' === $value || 0 === $value ) { continue; } $clean[ $field ] = $value; } return $clean; } /** * Resolves the user a call applies to. * * @param int $user_id Requested user, 0 for the current one. * * @return int The user ID, or 0 when there is nobody logged in. */ private function user( int $user_id ): int { return $user_id > 0 ? $user_id : get_current_user_id(); } }