Skip to content

Commit

Permalink
Replace parser class with a code validation class
Browse files Browse the repository at this point in the history
  • Loading branch information
sheabunge committed Jul 1, 2019
1 parent a2596fd commit f5b38f5
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 137 deletions.
4 changes: 3 additions & 1 deletion MENTIONS.md
Expand Up @@ -22,4 +22,6 @@ This document is an effort to maintain a list of website and articles which reco
- [perun.net](https://www.perun.net/2012/12/05/wordpress-funktionen-aus-dem-admin-bereich-verwalten/)
- [Kinsta](https://kinsta.com/knowledgebase/add-code-wordpress-header-footer/)
- [Amethyst Website Design](https://amethystwebsitedesign.com/how-to-use-shortcodes-in-your-wordpress-website-sidebar/)

- [Charitable Donation Plugin](https://www.wpcharitable.com/documentation/3-ways-to-add-code-customizations-to-your-site/)
- [Hostenko](https://hostenko.com/wpcafe/plugins/code-snippets-add-custom-code-in-wordpress/)
- [Iconic](https://iconicwp.com/blog/redirect-users-woocommerce-login-registration/)
97 changes: 12 additions & 85 deletions php/admin-menus/class-edit-menu.php
Expand Up @@ -195,73 +195,6 @@ private function code_error_callback( $out ) {
return $m;
}

/**
* Format a list of tokens for display.
*
* @param array $list
*
* @return string
*/
private function format_list( $list ) {
$final = array_pop( $list );
if ( empty( $list ) ) {
return '<code>' . $final . '</code>';
}

$str = '<code>' . join( '</code>' . _x( ', ', 'list separator', 'code-snippets' ) . '<code>', $list ) . '</code>';
/* translators: 1: first list item, 2: second list item */
return sprintf( __( '%1$s and %1$s', 'code-snippets' ), $str, '<code>' . $final . '</code>' );
}

/**
* Validate snippet code without executing it.
*
* @param Code_Snippet $snippet Snippet to validate.
*
* @return string An error message if an error was detected, an empty string if no errors were detected.
*/
private function dry_validate_code( Code_Snippet $snippet ) {
$parser = new Code_Snippets_Code_Parser( $snippet->code );
$parser->parse();

if ( $parse_errors = $parser->get_parse_errors() ) {
return $parse_errors[0];
}

if ( $snippet_functions = $parser->get_defined_functions() ) {
// ensure that the snippet only defines unique functions within its own code
if ( count( $snippet_functions ) !== count( array_flip( $snippet_functions ) ) ) {
return __( 'Snippet contains duplicate function declarations', 'code-snippets' );
}

$defined_functions = get_defined_functions();
$defined_functions = array_merge( $defined_functions['internal'], $defined_functions['user'] );
$duplicate_functions = array_intersect( $defined_functions, $snippet_functions );

if ( count( $duplicate_functions ) ) {
/* translators: %s: PHP function name */
$text = _n( 'Cannot redefine the %s function.', 'Cannot redefine the %s functions', count( $duplicate_functions ), 'code-snippets' );
return sprintf( $text, $this->format_list( $duplicate_functions ) );
}
}

if ( $snippet_classes = $parser->get_declared_classes() ) {
// ensure that the snippet only defines unique functions within its own code
if ( count( $snippet_classes ) !== count( array_flip( $snippet_classes ) ) ) {
return __( 'Snippet contains duplicate class declarations.', 'code-snippets' );
}

$duplicate_classes = array_intersect( get_declared_classes(), $snippet_classes );
if ( count( $duplicate_classes ) ) {
/* translators: %s: PHP function name */
$text = _n( 'Cannot redefine the %s class.', 'Cannot redefine the %s classes', count( $duplicate_classes ), 'code-snippets' );
return sprintf( $text, $this->format_list( $duplicate_classes ) );
}
}

return '';
}

/**
* Validate the snippet code before saving to database
*
Expand Down Expand Up @@ -323,8 +256,10 @@ private function save_posted_snippet() {

/* Deactivate snippet if code contains errors */
if ( $snippet->active && 'single-use' !== $snippet->scope ) {
$code_error = $this->dry_validate_code( $snippet );
$validator = new Code_Snippets_Validator( $snippet->code );
$code_error = $validator->validate();

var_dump($code_error);
if ( ! $code_error ) {
$code_error = $this->test_code( $snippet );
}
Expand Down Expand Up @@ -525,7 +460,9 @@ private function get_snippet_error( $snippet_id ) {
return false;
}

if ( $error = $this->dry_validate_code( $snippet ) ) {
$validator = new Code_Snippets_Validator( $snippet->code );

if ( $error = $validator->validate() ) {
return $error;
}

Expand Down Expand Up @@ -561,22 +498,12 @@ protected function print_messages() {

if ( isset( $_REQUEST['id'] ) && $error = $this->get_snippet_error( $_REQUEST['id'] ) ) {

if ( is_array( $error ) ) {

printf(
'<div id="message" class="error fade"><p>%s</p><p><strong>%s</strong></p></div>',
/* translators: %d: line of file where error originated */
sprintf( __( 'The snippet has been deactivated due to an error on line %d:', 'code-snippets' ), $error['line'] ),
$error['message']
);
} else {

printf(
'<div id="message" class="error fade"><p>%s</p><p><strong>%s</strong></p></div>',
/* translators: %d: line of file where error originated */
__( 'The snippet has been deactivated due to an error in its code:', 'code-snippets' ), $error
);
}
printf(
'<div id="message" class="error fade"><p>%s</p><p><strong>%s</strong></p></div>',
/* translators: %d: line of file where error originated */
sprintf( __( 'The snippet has been deactivated due to an error on line %d:', 'code-snippets' ), $error['line'] ),
$error['message']
);

} else {
echo '<div id="message" class="error fade"><p>', __( 'The snippet has been deactivated due to an error in the code.', 'code-snippets' ), '</p></div>';
Expand Down
123 changes: 72 additions & 51 deletions php/class-code-parser.php → php/class-validator.php
Expand Up @@ -5,7 +5,7 @@
*
* @package Code_Snippets
*/
class Code_Snippets_Code_Parser {
class Code_Snippets_Validator {

/**
* @var string
Expand All @@ -31,11 +31,12 @@ class Code_Snippets_Code_Parser {
*/
private $length;

private $class_names = array();

private $function_names = array();

private $errors = array();
/**
* Array to keep track of the various function, class and interface identifiers which have been defined.
*
* @var array
*/
private $defined_identifiers = array();

/**
* Class constructor.
Expand Down Expand Up @@ -75,18 +76,42 @@ private function peek() {
* @return bool Whether the pointer was advanced.
*/
private function next() {
if ( $this->end()) {
if ( $this->end() ) {
return false;
}

$this->current++;
return true;
}

/**
* Parse the provided tokens.
*/
public function parse() {
private function check_duplicate_identifier( $type, $identifier ) {

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

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

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

default:
return false;
}
}

$duplicate = in_array( $identifier, $this->defined_identifiers[ $type ], true );
array_unshift( $this->defined_identifiers[ $type ], $identifier );
return $duplicate;
}

public function validate() {

while ( ! $this->end() ) {
$token = $this->peek();
Expand All @@ -111,21 +136,42 @@ public function parse() {

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

// add the discovered class or function name to an appropriate array
if ( T_CLASS === $structure_type ) {
$this->class_names[] = $token[1];
} elseif ( T_FUNCTION === $structure_type ) {
$this->function_names[] = $token[1];
// 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;
if ( T_CLASS !== $structure_type ) {
continue;
}

// find the opening brace for the class
while ( ! $this->end() && '{' !== $token ) {
Expand All @@ -149,38 +195,13 @@ public function parse() {

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

return true;
}

/**
* Retrieve a list of functions defined in the code.
*
* @return array
*/
public function get_defined_functions() {
return $this->function_names;
}

/**
* Retrieve a list of classes declared in the code.
*
* @return array
*/
public function get_declared_classes() {
return $this->class_names;
}

/**
* Retrieve the errors encountered when parsing.
*
* @return array
*/
public function get_parse_errors() {
return $this->errors;
return false;
}
}

0 comments on commit f5b38f5

Please sign in to comment.