File "AllFieldsTag.php"

Full Path: /home/jovision/public_html/wp/wp-content/plugins/wpforms-lite/src/Emails/AllFieldsTag.php
File size: 4.45 KB
MIME-type: text/x-php
Charset: utf-8

<?php

namespace WPForms\Emails;

/**
 * The {all_fields} smart tag parameters.
 *
 * The tag is not a registered smart tag: mailers replace it literally after
 * regular smart tags are processed. This class centralizes recognizing the
 * tag with optional parameters and deciding which fields its parameters
 * exclude from the rendered output.
 *
 * @since 2.0.2
 */
class AllFieldsTag {

	/**
	 * Pattern matching the {all_fields} tag with optional parameters.
	 *
	 * Mirrors the smart tag recognition regex in wpforms_get_all_smart_tags(),
	 * so any {all_fields ...} variant the tag parser captures is replaced here
	 * instead of leaking verbatim into the sent message.
	 *
	 * @since 2.0.2
	 */
	const PATTERN = '~\{all_fields([ =][^\n}]*)?\}~';

	/**
	 * Parse tag parameters into exclusion options.
	 *
	 * The `exclude` parameter accepts a comma-separated list of field IDs
	 * and/or the keyword `hidden`, which excludes all Hidden-type fields.
	 * The value may be quoted or bare. Unknown tokens are ignored.
	 *
	 * @since 2.0.2
	 *
	 * @param string $params Parameters part of the tag.
	 *
	 * @return array Empty array when nothing is excluded, otherwise [ 'ids' => int[], 'hidden' => bool ].
	 */
	public static function parse( string $params ): array {

		if ( ! preg_match( '/(?:^|\s)exclude=(?:(["\'])(.+?)\1|([^\s"\'}]+))/', $params, $matches ) ) {
			return [];
		}

		$options = [
			'ids'    => [],
			'hidden' => false,
		];

		foreach ( explode( ',', $matches[3] ?? $matches[2] ) as $token ) {
			$token = strtolower( trim( $token ) );

			if ( $token === 'hidden' ) {
				$options['hidden'] = true;

				continue;
			}

			// Plain field IDs and repeater instance IDs like `7_2`, which absint() maps to the base field ID.
			if ( preg_match( '/^\d+(?:_\d+)?$/', $token ) ) {
				$options['ids'][] = absint( $token );
			}
		}

		$options['ids'] = array_values( array_unique( $options['ids'] ) );

		return $options['ids'] || $options['hidden'] ? $options : [];
	}

	/**
	 * Expand excluded container IDs with the IDs of the fields inside them.
	 *
	 * Excluding a Layout or Repeater field excludes its child fields as well,
	 * regardless of whether a mailer renders them via the container or as
	 * flat top-level rows.
	 *
	 * @since 2.0.2
	 *
	 * @param array $options   Options returned by self::parse().
	 * @param array $form_data Form data.
	 *
	 * @return array
	 */
	public static function expand( array $options, array $form_data ): array {

		if ( empty( $options['ids'] ) || empty( $form_data['fields'] ) ) {
			return $options;
		}

		foreach ( (array) $form_data['fields'] as $field ) {
			if (
				! is_array( $field ) ||
				! in_array( $field['type'] ?? '', [ 'layout', 'repeater' ], true ) ||
				! in_array( absint( $field['id'] ?? 0 ), $options['ids'], true )
			) {
				continue;
			}

			foreach ( (array) ( $field['columns'] ?? [] ) as $column ) {
				foreach ( (array) ( $column['fields'] ?? [] ) as $child_id ) {
					$options['ids'][] = absint( $child_id );
				}
			}
		}

		$options['ids'] = array_values( array_unique( $options['ids'] ) );

		return $options;
	}

	/**
	 * Replace every {all_fields} tag in a text, honoring its parameters.
	 *
	 * Identical option sets are rendered once per text.
	 *
	 * @since 2.0.2
	 *
	 * @param string   $text   Text to process.
	 * @param callable $render Renderer receiving options returned by self::parse(), returning the replacement.
	 *
	 * @return string
	 */
	public static function replace( string $text, callable $render ): string {

		$cache = [];

		return (string) preg_replace_callback(
			self::PATTERN,
			static function ( $matches ) use ( &$cache, $render ) {

				$options   = self::parse( $matches[1] ?? '' );
				$cache_key = (string) wp_json_encode( $options );

				if ( ! isset( $cache[ $cache_key ] ) ) {
					$cache[ $cache_key ] = (string) $render( $options );
				}

				return $cache[ $cache_key ];
			},
			$text
		);
	}

	/**
	 * Whether a field is excluded by the given options.
	 *
	 * @since 2.0.2
	 *
	 * @param array $field   Field data.
	 * @param array $options Options returned by self::parse().
	 *
	 * @return bool
	 */
	public static function is_excluded( array $field, array $options ): bool {

		if ( ! $options ) {
			return false;
		}

		if ( ! empty( $options['hidden'] ) && ( $field['type'] ?? '' ) === 'hidden' ) {
			return true;
		}

		// Repeater instances carry derived IDs like `7_2`; absint() maps them to the base field ID.
		return in_array( absint( $field['id'] ?? 0 ), $options['ids'] ?? [], true );
	}
}