within_complexity_limits( $conditions ) ) { return new \WP_Error( 'invalid_rule_conditions', __( 'Imported rule conditions are too large, deeply nested, or malformed.', 'glovario' ) ); } $known = array( 'version', 'date', 'auth', 'roles', 'post_types', 'post_ids', 'locales', 'glovario_translation' ); $allowed_custom = apply_filters( 'glovario_import_allowed_condition_keys', array(), $conditions ); $allowed_custom = array_filter( array_map( 'sanitize_key', is_array( $allowed_custom ) ? $allowed_custom : array() ) ); $unknown = array_diff( array_keys( $conditions ), $known, $allowed_custom ); if ( $unknown ) { return new \WP_Error( 'unknown_rule_condition', __( 'Imported rule conditions contain unsupported fields.', 'glovario' ) ); } $version = $conditions['version'] ?? 1; if ( 1 !== $version && '1' !== $version ) { return new \WP_Error( 'invalid_rule_condition_version', __( 'Imported rule conditions use an unsupported schema version.', 'glovario' ) ); } $date = $conditions['date'] ?? array(); if ( ! is_array( $date ) || array_diff( array_keys( $date ), array( 'from', 'until' ) ) ) { return new \WP_Error( 'invalid_rule_date', __( 'Imported rule dates are invalid.', 'glovario' ) ); } $from = $date['from'] ?? ''; $until = $date['until'] ?? ''; if ( ! $this->valid_date( $from ) || ! $this->valid_date( $until ) ) { return new \WP_Error( 'invalid_rule_date', __( 'Imported rule dates must use YYYY-MM-DDTHH:MM.', 'glovario' ) ); } $auth = $conditions['auth'] ?? 'everyone'; if ( ! is_string( $auth ) || ! in_array( $auth, array( 'everyone', 'logged_in', 'logged_out' ), true ) ) { return new \WP_Error( 'invalid_rule_auth', __( 'Imported rule authentication conditions are invalid.', 'glovario' ) ); } $roles = $this->key_list( $conditions['roles'] ?? array(), 100 ); $post_types = $this->key_list( $conditions['post_types'] ?? array(), 100 ); $post_ids = $this->id_list( $conditions['post_ids'] ?? array(), 500 ); $locales = $this->locale_list( $conditions['locales'] ?? array(), 100 ); foreach ( array( $roles, $post_types, $post_ids, $locales ) as $validated ) { if ( is_wp_error( $validated ) ) { return $validated; } } $normalized = array( 'version' => 1, 'date' => array( 'from' => $from, 'until' => $until, ), 'auth' => $auth, 'roles' => $roles, 'post_types' => $post_types, 'post_ids' => $post_ids, 'locales' => $locales, ); if ( isset( $conditions['glovario_translation'] ) ) { $marker = $this->translation_marker( $conditions['glovario_translation'] ); if ( is_wp_error( $marker ) ) { return $marker; } $normalized['glovario_translation'] = $marker; } foreach ( $allowed_custom as $key ) { if ( array_key_exists( $key, $conditions ) ) { $normalized[ $key ] = $conditions[ $key ]; } } $filtered = apply_filters( 'glovario_validated_imported_rule_conditions', $normalized, $conditions ); if ( is_wp_error( $filtered ) ) { return $filtered; } return is_array( $filtered ) && $this->within_complexity_limits( $filtered ) ? $filtered : new \WP_Error( 'invalid_filtered_rule_conditions', __( 'Imported rule conditions were rejected by an integration.', 'glovario' ) ); } private function key_list( $values, $maximum ) { if ( ! is_array( $values ) || count( $values ) > $maximum || array_values( $values ) !== $values ) { return new \WP_Error( 'invalid_rule_key_list', __( 'Imported rule role or post-type conditions are invalid.', 'glovario' ) ); } $clean = array(); foreach ( $values as $value ) { if ( ! is_string( $value ) || '' === $value || strlen( $value ) > 64 || sanitize_key( $value ) !== $value ) { return new \WP_Error( 'invalid_rule_key_list', __( 'Imported rule role or post-type conditions are invalid.', 'glovario' ) ); } $clean[] = $value; } return array_values( array_unique( $clean ) ); } private function id_list( $values, $maximum ) { if ( ! is_array( $values ) || count( $values ) > $maximum || array_values( $values ) !== $values ) { return new \WP_Error( 'invalid_rule_post_ids', __( 'Imported rule post IDs are invalid.', 'glovario' ) ); } $clean = array(); foreach ( $values as $value ) { if ( ( ! is_int( $value ) && ! ctype_digit( (string) $value ) ) || absint( $value ) < 1 ) { return new \WP_Error( 'invalid_rule_post_ids', __( 'Imported rule post IDs are invalid.', 'glovario' ) ); } $clean[] = absint( $value ); } return array_values( array_unique( $clean ) ); } private function locale_list( $values, $maximum ) { if ( ! is_array( $values ) || count( $values ) > $maximum || array_values( $values ) !== $values ) { return new \WP_Error( 'invalid_rule_locales', __( 'Imported rule locales are invalid.', 'glovario' ) ); } $clean = array(); foreach ( $values as $value ) { if ( ! is_string( $value ) || '' === $value || strlen( $value ) > 64 || sanitize_locale_name( $value ) !== $value ) { return new \WP_Error( 'invalid_rule_locales', __( 'Imported rule locales are invalid.', 'glovario' ) ); } $clean[] = $value; } return array_values( array_unique( $clean ) ); } private function translation_marker( $marker ) { if ( ! is_array( $marker ) || array_diff( array_keys( $marker ), array( 'managed', 'source_locale', 'provider' ) ) ) { return new \WP_Error( 'invalid_translation_marker', __( 'Imported managed-translation metadata is invalid.', 'glovario' ) ); } $managed = $marker['managed'] ?? false; $source = $marker['source_locale'] ?? ''; $provider = $marker['provider'] ?? ''; if ( ! is_bool( $managed ) || ! is_string( $source ) || '' === $source || strlen( $source ) > 64 || sanitize_locale_name( $source ) !== $source || ! is_string( $provider ) || '' === $provider || strlen( $provider ) > 64 || sanitize_key( $provider ) !== $provider ) { return new \WP_Error( 'invalid_translation_marker', __( 'Imported managed-translation metadata is invalid.', 'glovario' ) ); } return array( 'managed' => $managed, 'source_locale' => $source, 'provider' => $provider, ); } private function valid_date( $value ) { if ( ! is_string( $value ) || ( '' !== $value && 1 !== preg_match( '/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/', $value ) ) ) { return false; } if ( '' === $value ) { return true; } $date = \DateTimeImmutable::createFromFormat( '!Y-m-d\TH:i', $value, wp_timezone() ); $errors = \DateTimeImmutable::getLastErrors(); return false !== $date && ( false === $errors || ( 0 === $errors['warning_count'] && 0 === $errors['error_count'] ) ) && $date->format( 'Y-m-d\TH:i' ) === $value; } private function within_complexity_limits( $conditions ) { $encoded = wp_json_encode( $conditions ); if ( false === $encoded || strlen( $encoded ) > self::MAX_SERIALIZED_BYTES ) { return false; } $nodes = 0; return $this->measure( $conditions, 1, $nodes ); } private function measure( $value, $depth, &$nodes ) { ++$nodes; if ( $depth > self::MAX_DEPTH || $nodes > self::MAX_NODES ) { return false; } if ( is_array( $value ) ) { foreach ( $value as $child ) { if ( ! $this->measure( $child, $depth + 1, $nodes ) ) { return false; } } } return true; } }