Your IP : 216.73.216.215


Current Path : /home/flapst5/ekmtoronto.com/wp-content/plugins/code-snippets/php/Utils/
Upload File :
Current File : /home/flapst5/ekmtoronto.com/wp-content/plugins/code-snippets/php/Utils/Validator.php

<?php

namespace Code_Snippets\Utils;

/**
 * Validates code prior to execution.
 *
 * @package Code_Snippets
 */
class Validator {

	/**
	 * Code to validate.
	 *
	 * @var string
	 */
	private string $code;

	/**
	 * List of tokens.
	 *
	 * @var array<string>
	 */
	private array $tokens;

	/**
	 * The index of the token currently being examined.
	 *
	 * @var int
	 */
	private int $current;

	/**
	 * The total number of tokens.
	 *
	 * @var int
	 */
	private int $length;

	/**
	 * Array to keep track of the various function, class and interface identifiers which have been defined.
	 *
	 * @var array<string, string[]>
	 */
	private array $defined_identifiers = [];

	/**
	 * Exclude certain tokens from being checked.
	 *
	 * @var array<string, string[]>
	 */
	private array $exceptions = [];

	/**
	 * Class constructor.
	 *
	 * @param string $code Snippet code for parsing.
	 */
	public function __construct( string $code ) {
		$this->code = $code;
		$this->tokens = token_get_all( "<?php\n" . $this->code );
		$this->length = count( $this->tokens );
		$this->current = 0;
	}

	/**
	 * Determine whether the parser has reached the end of the list of tokens.
	 *
	 * @return bool
	 */
	private function end(): bool {
		return $this->current === $this->length;
	}

	/**
	 * Retrieve the next token without moving the pointer
	 *
	 * @return string|array<string|int>|null The current token if the list has not been expended, null otherwise.
	 */
	private function peek() {
		return $this->end() ? null : $this->tokens[ $this->current ];
	}

	/**
	 * Move the pointer to the next token, if there is one.
	 *
	 * If the first argument is provided, only move the pointer if the tokens match.
	 */
	private function next() {
		if ( ! $this->end() ) {
			++$this->current;
		}
	}

	/**
	 * Check whether a particular identifier has been used previously.
	 *
	 * @param string $type       Which type of identifier this is. Supports T_FUNCTION, T_CLASS and T_INTERFACE.
	 * @param string $identifier The name of the identifier itself.
	 *
	 * @return bool true if the identifier is not unique.
	 */
	private function check_duplicate_identifier( string $type, string $identifier ): bool {
		$identifier = strtolower( ltrim( $identifier, '\\' ) );
		$namespaced_identifier = 'code_snippets\\' . $identifier;

		if ( ! isset( $this->defined_identifiers[ $type ] ) ) {
			switch ( $type ) {
				case T_FUNCTION:
					$defined_functions = get_defined_functions();
					$this->defined_identifiers[ T_FUNCTION ] = array_map(
						'strtolower',
						array_merge( $defined_functions['internal'], $defined_functions['user'] )
					);
					break;

				case T_CLASS:
					$this->defined_identifiers[ T_CLASS ] = array_map( 'strtolower', get_declared_classes() );
					break;

				case T_INTERFACE:
					$this->defined_identifiers[ T_INTERFACE ] = array_map( 'strtolower', get_declared_interfaces() );
					break;

				default:
					return false;
			}
		}

		$duplicate_identifier = in_array( $identifier, $this->defined_identifiers[ $type ], true );
		$duplicate_namespaced = in_array( $namespaced_identifier, $this->defined_identifiers[ $type ], true );
		$exceptions = $this->exceptions[ $type ] ?? [];
		$exception_identifier = in_array( $identifier, $exceptions, true );
		$exception_namespaced = in_array( $namespaced_identifier, $exceptions, true );

		array_unshift( $this->defined_identifiers[ $type ], $identifier );

		return ( $duplicate_identifier && ! $exception_identifier ) || ( $duplicate_namespaced && ! $exception_namespaced );
	}

	/**
	 * Validate the given PHP code and return the result.
	 *
	 * @return array<string, mixed>|false Array containing message if an error was encountered, false if validation was successful.
	 */
	public function validate() {

		while ( ! $this->end() ) {
			$token = $this->peek();
			$this->next();

			if ( ! is_array( $token ) ) {
				continue;
			}

			// If this is a function or class exists check, then allow this function or class to be defined.
			if ( T_STRING === $token[0] && ( 'function_exists' === $token[1] || 'class_exists' === $token[1] ) ) {
				$type = 'function_exists' === $token[1] ? T_FUNCTION : T_CLASS;

				// Eat tokens until we find the function or class name.
				while ( ! $this->end() && T_CONSTANT_ENCAPSED_STRING !== $token[0] ) {
					$token = $this->peek();
					$this->next();
				}

				// Add the identifier to the list of exceptions.
				$identifier = strtolower( ltrim( trim( $token[1], '\'"' ), '\\' ) );

				if ( '' !== $identifier ) {
					$this->exceptions[ $type ] = $this->exceptions[ $type ] ?? [];
					$this->exceptions[ $type ][] = $identifier;
				}
				continue;
			}

			// If we have a double colon, followed by a class, then consume it before the next section.
			if ( T_DOUBLE_COLON === $token[0] ) {
				$token = $this->peek();
				$this->next();

				if ( T_CLASS === $token[0] ) {
					$this->next();
					$token = $this->peek();
				}
			}

			// Only look for class and function declaration tokens.
			if ( T_CLASS !== $token[0] && T_FUNCTION !== $token[0] ) {
				continue;
			}

			/**
			 * Ensure the type of $token is inferred correctly.
			 *
			 * @var string|array<string|int> $token
			 */
			$structure_type = $token[0];

			// Continue eating tokens until we find the name of the class or function.
			while ( ! $this->end() && T_STRING !== $token[0] &&
			        ( T_FUNCTION !== $structure_type || '(' !== $token ) && ( T_CLASS !== $structure_type || '{' !== $token ) ) {
				$token = $this->peek();
				$this->next();
			}

			// If we've eaten all the tokens without discovering a name, then there must be a syntax error, so return appropriately.
			if ( $this->end() ) {
				return array(
					'message' => __( 'Parse error: syntax error, unexpected end of snippet.', 'code-snippets' ),
					'line'    => $token[2],
				);
			}

			// If the function or class is anonymous, with no name, then no need to check.
			if ( ! ( T_FUNCTION === $structure_type && '(' === $token ) && ! ( T_CLASS === $structure_type && '{' === $token ) ) {

				// Check whether the name has already been defined.
				if ( $this->check_duplicate_identifier( $structure_type, $token[1] ) ) {
					switch ( $structure_type ) {
						case T_FUNCTION:
							/* translators: %s: PHP function name */
							$message = __( 'Cannot redeclare function %s.', 'code-snippets' );
							break;
						case T_CLASS:
							/* translators: %s: PHP class name */
							$message = __( 'Cannot redeclare class %s.', 'code-snippets' );
							break;
						case T_INTERFACE:
							/* translators: %s: PHP interface name */
							$message = __( 'Cannot redeclare interface %s.', 'code-snippets' );
							break;
						default:
							/* translators: %s: PHP identifier name*/
							$message = __( 'Cannot redeclare %s.', 'code-snippets' );
					}

					return array(
						'message' => sprintf( $message, $token[1] ),
						'line'    => $token[2],
					);
				}
			}

			// If we have entered into a class, eat tokens until we find the closing brace.
			if ( T_CLASS !== $structure_type ) {
				continue;
			}

			// Find the opening brace for the class.
			while ( ! $this->end() && '{' !== $token ) {
				$token = $this->peek();
				$this->next();
			}

			// Continue traversing the class tokens until we have found the class closing brace.
			$depth = 1;
			while ( ! $this->end() && $depth > 0 ) {
				$token = $this->peek();

				if ( '{' === $token ) {
					++$depth;
				} elseif ( '}' === $token ) {
					--$depth;
				}

				$this->next();
			}

			// If we did not make it out of the class, then there's a problem.
			if ( $depth > 0 ) {
				return array(
					'message' => __( 'Parse error: syntax error, unexpected end of snippet.', 'code-snippets' ),
					'line'    => $token[2],
				);
			}
		}

		return false;
	}
}