File "FrmCssScopeHelper.php"

Full Path: /home/adniftyx/public_html/wp-content/plugins/formidable/classes/helpers/FrmCssScopeHelper.php
File size: 6.45 KB
MIME-type: text/x-php
Charset: utf-8

<?php
if ( ! defined( 'ABSPATH' ) ) {
	die( 'You are not allowed to call this page directly.' );
}

class FrmCssScopeHelper {

	/**
	 * Nest the CSS.
	 * This function nests the CSS by adding the class name prefix to the selectors.
	 *
	 * @param string $css
	 * @param string $class_name
	 *
	 * @return string
	 */
	public function nest( $css, $class_name ) {
		// Remove CSS comments but preserve newlines
		$css = preg_replace( '/\/\*.*?\*\//s', '', $css );

		$output = array();
		$css    = trim( $css );
		$length = strlen( $css );
		$i      = 0;
		$buffer = '';

		while ( $i < $length ) {
			$char = $css[ $i ];

			if ( '@' === $char ) {
				$brace_pos = strpos( $css, '{', $i );

				if ( false === $brace_pos ) {
					$buffer .= $char;
					++$i;
					continue;
				}

				$rule          = substr( $css, $i, $brace_pos - $i );
				$closing_brace = $this->find_matching_brace( $css, $brace_pos );
				$inner_content = substr( $css, $brace_pos + 1, $closing_brace - $brace_pos - 1 );

				// Don't nest keyframes content
				if ( str_contains( $rule, '@keyframes' ) ) {
					$output[] = "\n" . $rule . ' {' . $inner_content . '}' . "\n";
				} else {
					$output[] = "\n" . $rule . ' {';
					$output[] = $this->nest( $inner_content, $class_name );
					$output[] = '}' . "\n";
				}

				$i      = $closing_brace + 1;
				$buffer = '';
				continue;
			}//end if

			if ( '{' === $char ) {
				$selector      = trim( $buffer );
				$closing_brace = $this->find_matching_brace( $css, $i );
				$declarations  = substr( $css, $i + 1, $closing_brace - $i - 1 );

				// Preserve indentation and formatting of declarations
				$declarations = $this->preserve_declaration_formatting( $declarations );

				if ( '' !== $selector && '' !== trim( $declarations ) ) {
					// Handle multiple selectors
					$selectors          = array_map( 'trim', explode( ',', $selector ) );
					$prefixed_selectors = array();

					foreach ( $selectors as $single_selector ) {
						if ( '' !== $single_selector ) {
							$prefixed_selectors[] = '.' . $class_name . ' ' . $single_selector;
						}
					}

					if ( $prefixed_selectors ) {
						$output[] = "\n" . implode( ',' . "\n", $prefixed_selectors ) . ' {' . $declarations . '}' . "\n";
					}
				}

				$i      = $closing_brace + 1;
				$buffer = '';
				continue;
			}//end if

			$buffer .= $char;
			++$i;
		}//end while

		return implode( '', $output );
	}

	/**
	 * Unnest the CSS.
	 * This function unnests the CSS by removing the class name prefix from the selectors.
	 *
	 * @param string $css
	 * @param string $class_name
	 *
	 * @return string
	 */
	public function unnest( $css, $class_name ) {
		// Remove CSS comments but preserve newlines
		$css = preg_replace( '/\/\*.*?\*\//s', '', $css );

		$output        = array();
		$css           = trim( $css );
		$length        = strlen( $css );
		$i             = 0;
		$buffer        = '';
		$prefix        = '.' . $class_name . ' ';
		$prefix_length = strlen( $prefix );

		while ( $i < $length ) {
			$char = $css[ $i ];

			if ( '@' === $char ) {
				$brace_pos = strpos( $css, '{', $i );

				if ( false === $brace_pos ) {
					$buffer .= $char;
					++$i;

					continue;
				}

				$rule          = substr( $css, $i, $brace_pos - $i );
				$closing_brace = $this->find_matching_brace( $css, $brace_pos );
				$inner_content = substr( $css, $brace_pos + 1, $closing_brace - $brace_pos - 1 );

				$output[] = "\n" . $rule . ' {';
				$output[] = $this->unnest( $inner_content, $class_name );
				$output[] = '}' . "\n";

				$i      = $closing_brace + 1;
				$buffer = '';
				continue;
			}//end if

			if ( '{' === $char ) {
				$selector      = trim( $buffer );
				$closing_brace = $this->find_matching_brace( $css, $i );
				$declarations  = substr( $css, $i + 1, $closing_brace - $i - 1 );

				// Preserve indentation and formatting of declarations
				$declarations = $this->preserve_declaration_formatting( $declarations );

				if ( '' !== $selector && '' !== trim( $declarations ) ) {
					// Handle multiple selectors
					$selectors            = array_filter(
						array_map( 'trim', explode( ',', $selector ) ),
						function ( $s ) {
							return '' !== $s;
						}
					);
					$unprefixed_selectors = array();

					foreach ( $selectors as $single_selector ) {
						$unprefixed_selectors[] = str_starts_with( $single_selector, $prefix )
							? trim( substr( $single_selector, $prefix_length ) )
							: $single_selector;
					}

					if ( $unprefixed_selectors ) {
						$output[] = "\n" . implode( ',' . "\n", $unprefixed_selectors ) . ' {' . $declarations . '}' . "\n";
					}
				}

				$i      = $closing_brace + 1;
				$buffer = '';
				continue;
			}//end if

			$buffer .= $char;
			++$i;
		}//end while
		return implode( '', $output );
	}

	/**
	 * Preserve declaration formatting with proper indentation.
	 *
	 * @param string $declarations
	 *
	 * @return string
	 */
	private function preserve_declaration_formatting( $declarations ) {
		// Trim the entire block but keep internal structure
		$declarations = trim( $declarations );

		if ( '' === $declarations ) {
			return '';
		}

		// Check if declarations are already on multiple lines
		if ( str_contains( $declarations, "\n" ) ) {
			// Already formatted - preserve it
			$lines           = explode( "\n", $declarations );
			$formatted_lines = array();

			foreach ( $lines as $line ) {
				$trimmed = trim( $line );

				if ( '' !== $trimmed ) {
					$formatted_lines[] = "\n\t" . $trimmed;
				}
			}

			return implode( '', $formatted_lines ) . "\n";
		}

		// Single line - add minimal formatting
		return ' ' . $declarations . ' ';
	}

	/**
	 * Find the matching brace in the CSS.
	 *
	 * @param string $css
	 * @param int    $open_pos
	 *
	 * @return int
	 */
	private function find_matching_brace( $css, $open_pos ) {
		$level       = 1;
		$length      = strlen( $css );
		$in_string   = false;
		$string_char = '';

		for ( $i = $open_pos + 1; $i < $length; $i++ ) {
			$char = $css[ $i ];

			// Handle string literals to avoid matching braces inside strings
			if ( ( '"' === $char || "'" === $char ) && ( 0 === $i || '\\' !== $css[ $i - 1 ] ) ) {
				if ( ! $in_string ) {
					$in_string   = true;
					$string_char = $char;
				} elseif ( $char === $string_char ) {
					$in_string = false;
				}
				continue;
			}

			// Skip braces inside strings
			if ( $in_string ) {
				continue;
			}

			if ( '{' === $char ) {
				++$level;
			} elseif ( '}' === $char ) {
				--$level;

				if ( 0 === $level ) {
					return $i;
				}
			}
		}//end for

		return $length - 1;
	}
}//end class