swm-console-common.php
<?php
/**
* Description: Helper Console Build Feature.
* PHP version 8.0.1
*
* @category console-common
* @package SiteWatchman
* @author Oleg Klenitsky <klenitskiy.oleg@mail.ru>
* @version 1.0.0
* @license GPLv2 or later
*/
if ( ! defined( 'ABSPATH' ) ) {
exit();
}
if ( ob_get_length() > 0 ) {
ob_end_clean();
}
set_time_limit( 0 );
/**
* Description: Console error handler.
*
* @param integer $errno Number of error.
* @param string $errorstr Message of error.
*/
function swm_console_error_handler( $errno, $errorstr ) {
swm_console_error( $errorstr );
}
/**
* Description: Error output in json format.
*
* @param string $error Message of error.
*/
function swm_console_error( $error ) {
exit( json_encode( array( 'error' => $error ) ) );
}
/**
* Description: Saves newly defined variables to session.
*
* @param array $existing existing.
* @param array $current current.
* @param array $ignore ignore.
*/
function swm_save_variables( $existing, $current, $ignore ) {
$swm_console = get_option( 'swm_console', array() );
$new_vars = array_diff( array_keys( $current ), array_keys( $existing ) );
$user_vars = array_diff( $new_vars, $ignore );
$save_vars = array();
foreach ( $current as $key => $value ) {
if ( in_array( $key, $user_vars, true ) ) {
$save_vars[ $key ] = $value;
}
}
$export = var_export( $save_vars, true );
// special consideration for variables that are objects.
// http://www.thoughtlabs.com/2008/02/02/phps-mystical-__set_state-method/ .
$export = preg_replace_callback( '/(\w+)::__set_state/Ums', 'swm_class_set_state_check', $export );
$swm_console['console_vars'] = $export;
update_option( 'swm_console', $swm_console );
}
/**
* Description: Classes to be restored need to implement __set_state() function.
* If they don't have it, we will convert to stdClass object.
*
* @param object $matches Matches.
* @return object.
*/
function swm_class_set_state_check( $matches ) {
if ( method_exists( $matches[1], '__set_state' ) ) {
return $matches[0];
} else {
return '(object) ';
}
}
/**
* See: http://jan.kneschke.de/projects/php-shell
* Return int 0 if a executable statement is in the session buffer, non-zero otherwise.
*
* @param object $code Code for parse.
* @throws Exception Variable \'%s\' is not set.
* @throws Exception Variable \'%s\' is not a class.
* @throws Exception Variable %s (Class '%s') doesn't have a method named '%s'.
* @throws Exception Variable \'%s\' is not set.
* @throws Exception Variable \'%s\' is not a class.
* @throws Exception Variable %s (Class '%s') doesn't have a method named '%s'.
* @throws Exception Variable \'%s\' is not a array'.
* @throws Exception Class \'%s\' doesn\'t exist.
* @throws Exception Class '%s' doesn't have a method named '%s'.
* @return integer.
*/
function swm_parse_code( $code ) {
$swm_console = get_option( 'swm_console', array() );
// remove empty lines.
if ( trim( $code ) === '' ) {
return 1;
}
$t = token_get_all( '<?php ' . $code . ' ?>' );
// Need a semicolon to complete the statement.
$need_semicolon = 1;
// Need to add a return to eval-string.
$need_return = 1;
// A open multi-line comment.
$open_comment = 0;
// Code to be eval().
$eval = '';
// To track if we need more closing braces.
$braces = array();
// To track duplicate methods in a class declaration.
$methods = array();
// Tokens without whitespaces.
$ts = array();
foreach ( $t as $ndx => $token ) {
if ( is_array( $token ) ) {
$ignore = 0;
switch ( $token[0] ) {
case T_WHITESPACE:
case T_OPEN_TAG:
case T_CLOSE_TAG:
$ignore = 1;
break;
case T_FOREACH:
case T_DO:
case T_WHILE:
case T_FOR:
case T_IF:
case T_RETURN:
case T_CLASS:
case T_FUNCTION:
case T_INTERFACE:
case T_PRINT:
case T_ECHO:
case T_COMMENT:
case T_UNSET:
case T_INCLUDE:
case T_REQUIRE:
case T_INCLUDE_ONCE:
case T_REQUIRE_ONCE:
case T_TRY:
case T_SWITCH:
case T_DEFAULT:
case T_CASE:
case T_BREAK:
case T_DOC_COMMENT:
$need_return = 0;
break;
case T_EMPTY:
case T_ISSET:
case T_EVAL:
case T_EXIT:
case T_VARIABLE:
case T_STRING:
case T_NEW:
case T_EXTENDS:
case T_IMPLEMENTS:
case T_OBJECT_OPERATOR:
case T_DOUBLE_COLON:
case T_INSTANCEOF:
case T_CATCH:
case T_THROW:
case T_ELSE:
case T_AS:
case T_LNUMBER:
case T_DNUMBER:
case T_CONSTANT_ENCAPSED_STRING:
case T_ENCAPSED_AND_WHITESPACE:
case T_CHARACTER:
case T_ARRAY:
case T_DOUBLE_ARROW:
case T_CONST:
case T_PUBLIC:
case T_PROTECTED:
case T_PRIVATE:
case T_ABSTRACT:
case T_STATIC:
case T_VAR:
case T_INC:
case T_DEC:
case T_SL:
case T_SL_EQUAL:
case T_SR:
case T_SR_EQUAL:
case T_IS_EQUAL:
case T_IS_IDENTICAL:
case T_IS_GREATER_OR_EQUAL:
case T_IS_SMALLER_OR_EQUAL:
case T_BOOLEAN_OR:
case T_LOGICAL_OR:
case T_BOOLEAN_AND:
case T_LOGICAL_AND:
case T_LOGICAL_XOR:
case T_MINUS_EQUAL:
case T_PLUS_EQUAL:
case T_MUL_EQUAL:
case T_DIV_EQUAL:
case T_MOD_EQUAL:
case T_XOR_EQUAL:
case T_AND_EQUAL:
case T_OR_EQUAL:
case T_FUNC_C:
case T_CLASS_C:
case T_LINE:
case T_FILE:
case T_BOOL_CAST:
case T_INT_CAST:
case T_STRING_CAST:
/* just go on */
break;
default:
/* debug unknown tags*/
error_log( sprintf( 'unknown tag: %d (%s): %s' . PHP_EOL, $token[0], token_name( $token[0] ), $token[1] ) );
break;
}
if ( ! $ignore ) {
$eval .= $token[1] . ' ';
$ts[] = array(
'token' => $token[0],
'value' => $token[1],
);
}
} else {
$ts[] = array(
'token' => $token,
'value' => '',
);
$last = count( $ts ) - 1;
switch ( $token ) {
case '(':
// Walk backwards through the tokens.
if ( $last >= 4 &&
T_STRING === $ts[ $last - 1 ]['token'] &&
T_OBJECT_OPERATOR === $ts[ $last - 2 ]['token'] &&
')' === $ts[ $last - 3 ]['token'] ) {
// We can't know what func() is return, so we can't say if the method() exists or not.
} elseif ( $last >= 3 &&
// If we are not in a class definition.
T_CLASS !== $ts[0]['token'] &&
T_ABSTRACT !== $ts[0]['token'] &&
T_CLASS !== $ts[1]['token'] &&
T_STRING === $ts[ $last - 1 ]['token'] &&
T_OBJECT_OPERATOR === $ts[ $last - 2 ]['token'] &&
T_VARIABLE === $ts[ $last - 3 ]['token'] ) {
// $object -> method.
$in_catch = 0;
foreach ( $ts as $v ) {
if ( T_CATCH === $v['token'] ) {
$in_catch = 1;
}
}
if ( ! $in_catch ) {
// $object has to exist and has to be a object.
$objname = $ts[ $last - 3 ]['value'];
if ( ! isset( $GLOBALS[ ltrim( $objname, '$' ) ] ) ) {
throw new Exception( sprintf( 'Variable \'%s\' is not set', $objname ) );
}
$object = $GLOBALS[ ltrim( $objname, '$' ) ];
if ( ! is_object( $object ) ) {
throw new Exception( sprintf( 'Variable \'%s\' is not a class', $objname ) );
}
$method = $ts[ $last - 1 ]['value'];
if ( ! method_exists( $object, $method ) ) {
throw new Exception(
sprintf(
"Variable %s (Class '%s') doesn't have a method named '%s'",
$objname,
get_class( $object ),
$method
)
);
}
}
} elseif ( $last >= 3 &&
// if we are not in a class definition.
T_CLASS !== $ts[0]['token'] &&
T_VARIABLE === $ts[ $last - 1 ]['token'] &&
T_OBJECT_OPERATOR === $ts[ $last - 2 ]['token'] &&
T_VARIABLE === $ts[ $last - 3 ]['token'] ) {
// $object has to exist and has to be a object.
$objname = $ts[ $last - 3 ]['value'];
if ( ! isset( $GLOBALS[ ltrim( $objname, '$' ) ] ) ) {
throw new Exception( sprintf( 'Variable \'%s\' is not set', $objname ) );
}
$object = $GLOBALS[ ltrim( $objname, '$' ) ];
if ( ! is_object( $object ) ) {
throw new Exception( sprintf( 'Variable \'%s\' is not a class', $objname ) );
}
$methodname = $ts[ $last - 1 ]['value'];
if ( ! isset( $GLOBALS[ ltrim( $methodname, '$' ) ] ) ) {
throw new Exception( sprintf( 'Variable \'%s\' is not set', $methodname ) );
}
$method = $GLOBALS[ ltrim( $methodname, '$' ) ];
if ( ! method_exists( $object, $method ) ) {
throw new Exception(
sprintf(
"Variable %s (Class '%s') doesn't have a method named '%s'",
$objname,
get_class( $object ),
$method
)
);
}
} elseif ( $last >= 6 &&
// if we are not in a class definition.
T_CLASS !== $ts[0]['token'] &&
T_STRING === $ts[ $last - 1 ]['token'] &&
T_OBJECT_OPERATOR === $ts[ $last - 2 ]['token'] &&
']' === $ts[ $last - 3 ]['token'] &&
// might be anything as index.
'[' === $ts[ $last - 5 ]['token'] &&
T_VARIABLE === $ts[ $last - 6 ]['token'] ) {
// $object has to exist and has to be a object.
$objname = $ts[ $last - 6 ]['value'];
if ( ! isset( $GLOBALS[ ltrim( $objname, '$' ) ] ) ) {
throw new Exception( sprintf( 'Variable \'%s\' is not set', $objname ) );
}
$array = $GLOBALS[ ltrim( $objname, '$' ) ];
if ( ! is_array( $array ) ) {
throw new Exception( sprintf( 'Variable \'%s\' is not a array', $objname ) );
}
$andx = $ts[ $last - 4 ]['value'];
if ( ! isset( $array[ $andx ] ) ) {
throw new Exception( sprintf( '%s[\'%s\'] is not set', $objname, $andx ) );
}
$object = $array[ $andx ];
if ( ! is_object( $object ) ) {
throw new Exception( sprintf( 'Variable \'%s\' is not a class', $objname ) );
}
$method = $ts[ $last - 1 ]['value'];
if ( ! method_exists( $object, $method ) ) {
throw new Exception(
sprintf(
"Variable %s (Class '%s') doesn't have a method named '%s'",
$objname,
get_class( $object ),
$method
)
);
}
} elseif ( $last >= 3 &&
// if we are not in a class definition.
T_CLASS !== $ts[0]['token'] &&
T_STRING === $ts[ $last - 1 ]['token'] &&
T_DOUBLE_COLON === $ts[ $last - 2 ]['token'] &&
T_STRING === $ts[ $last - 3 ]['token'] ) {
// $object has to exist and has to be a object.
$classname = $ts[ $last - 3 ]['value'];
if ( ! class_exists( $classname ) ) {
throw new Exception( sprintf( 'Class \'%s\' doesn\'t exist', $classname ) );
}
$method = $ts[ $last - 1 ]['value'];
if ( ! in_array( $method, get_class_methods( $classname ) ) ) {
throw new Exception(
sprintf(
"Class '%s' doesn't have a method named '%s'",
$classname,
$method
)
);
}
} elseif ( $last >= 3 &&
// if we are not in a class definition.
T_CLASS !== $ts[0]['token'] &&
T_VARIABLE === $ts[ $last - 1 ]['token'] &&
T_DOUBLE_COLON === $ts[ $last - 2 ]['token'] &&
T_STRING === $ts[ $last - 3 ]['token'] ) {
// $object has to exist and has to be a object.
$classname = $ts[ $last - 3 ]['value'];
if ( ! class_exists( $classname ) ) {
throw new Exception( sprintf( 'Class \'%s\' doesn\'t exist', $classname ) );
}
$methodname = $ts[ $last - 1 ]['value'];
if ( ! isset( $GLOBALS[ ltrim( $methodname, '$' ) ] ) ) {
throw new Exception( sprintf( 'Variable \'%s\' is not set', $methodname ) );
}
$method = $GLOBALS[ ltrim( $methodname, '$' ) ];
if ( ! in_array( $method, get_class_methods( $classname ) ) ) {
throw new Exception(
sprintf(
"Class '%s' doesn't have a method named '%s'",
$classname,
$method
)
);
}
} elseif ( $last >= 2 &&
// if we are not in a class definition.
T_CLASS !== $ts[0]['token'] &&
T_STRING === $ts[ $last - 1 ]['token'] &&
T_NEW === $ts[ $last - 2 ]['token'] ) {
// don't care about this in a class ... { ... }.
$classname = $ts[ $last - 1 ]['value'];
if ( ! class_exists( $classname ) ) {
throw new Exception( sprintf( 'Class \'%s\' doesn\'t exist', $classname ) );
}
$r = new ReflectionClass( $classname );
if ( $r->isAbstract() ) {
throw new Exception( sprintf( "Can't instantiate abstract Class '%s'", $classname ) );
}
if ( ! $r->isInstantiable() ) {
throw new Exception( sprintf( 'Class \'%s\' can\'t be instantiated. Is the class abstract ?', $classname ) );
}
} elseif ( $last >= 2 &&
// if we are not in a class definition.
T_CLASS !== $ts[0]['token'] &&
T_STRING === $ts[ $last - 1 ]['token'] &&
T_FUNCTION === $ts[ $last - 2 ]['token'] ) {
// make sure we are not a in class definition.
$func = $ts[ $last - 1 ]['value'];
if ( function_exists( $func ) ) {
throw new Exception( sprintf( 'Function \'%s\' is already defined', $func ) );
}
} elseif ( $last >= 4 &&
T_CLASS === $ts[0]['token'] &&
T_STRING === $ts[1]['token'] &&
T_STRING === $ts[ $last - 1 ]['token'] &&
T_FUNCTION === $ts[ $last - 2 ]['token'] ) {
// make sure we are not a in class definition.
$func = $ts[ $last - 1 ]['value'];
$classname = $ts[1]['value'];
if ( isset( $methods[ $func ] ) ) {
throw new Exception( sprintf( "Can't redeclare method '%s' in Class '%s'", $func, $classname ) );
}
$methods[ $func ] = 1;
} elseif ( $last >= 1 &&
// if we are not in a class definition.
T_CLASS !== $ts[0]['token'] &&
T_ABSTRACT !== $ts[0]['token'] &&
T_CLASS !== $ts[1]['token'] &&
T_STRING === $ts[ $last - 1 ]['token'] ) {
$funcname = $ts[ $last - 1 ]['value'];
if ( ! function_exists( $funcname ) ) {
throw new Exception( sprintf( "Function %s() doesn't exist", $funcname ) );
}
} elseif ( $last >= 1 &&
// if we are not in a class definition.
T_CLASS !== $ts[0]['token'] &&
T_VARIABLE === $ts[ $last - 1 ]['token'] ) {
// $object has to exist and has to be a object.
$funcname = $ts[ $last - 1 ]['value'];
if ( ! isset( $GLOBALS[ ltrim( $funcname, '$' ) ] ) ) {
throw new Exception( sprintf( 'Variable \'%s\' is not set', $funcname ) );
}
$func = $GLOBALS[ ltrim( $funcname, '$' ) ];
if ( ! function_exists( $func ) ) {
throw new Exception( sprintf( "Function %s() doesn't exist", $func ) );
}
}
array_push( $braces, $token );
break;
case '{':
$need_return = 0;
if ( $last >= 2 &&
T_STRING === $ts[ $last - 1 ]['token'] &&
T_CLASS === $ts[ $last - 2 ]['token'] ) {
$classname = $ts[ $last - 1 ]['value'];
if ( class_exists( $classname, false ) ) {
throw new Exception( sprintf( "Class '%s' can't be redeclared", $classname ) );
}
} elseif ( $last >= 4 &&
T_STRING === $ts[ $last - 1 ]['token'] &&
T_EXTENDS === $ts[ $last - 2 ]['token'] &&
T_STRING === $ts[ $last - 3 ]['token'] &&
T_CLASS === $ts[ $last - 4 ]['token'] ) {
$classname = $ts[ $last - 3 ]['value'];
$extendsname = $ts[ $last - 1 ]['value'];
if ( class_exists( $classname, false ) ) {
throw new Exception(
sprintf(
"Class '%s' can't be redeclared",
$classname
)
);
}
if ( ! class_exists( $extendsname, true ) ) {
throw new Exception(
sprintf(
"Can't extend '%s' ... from not existing Class '%s'",
$classname,
$extendsname
)
);
}
} elseif ( $last >= 4 &&
T_STRING === $ts[ $last - 1 ]['token'] &&
T_IMPLEMENTS === $ts[ $last - 2 ]['token'] &&
T_STRING === $ts[ $last - 3 ]['token'] &&
T_CLASS === $ts[ $last - 4 ]['token'] ) {
// class name implements interface.
$classname = $ts[ $last - 3 ]['value'];
$implements = $ts[ $last - 1 ]['value'];
if ( class_exists( $classname, false ) ) {
throw new Exception(
sprintf(
"Class '%s' can't be redeclared",
$classname
)
);
}
if ( ! interface_exists( $implements, false ) ) {
throw new Exception(
sprintf(
"Can't implement not existing Interface '%s' for Class '%s'",
$implements,
$classname
)
);
}
}
array_push( $braces, $token );
break;
case '}':
$need_return = 0;
case ')':
array_pop( $braces );
break;
case '[':
// if we are not in a class definition.
if ( T_CLASS !== $ts[0]['token'] &&
T_ABSTRACT !== $ts[0]['token'] &&
T_CLASS !== $ts[1]['token'] &&
T_VARIABLE === $ts[ $last - 1 ]['token'] ) {
// $object has to exist and has to be a object.
$objname = $ts[ $last - 1 ]['value'];
if ( ! isset( $GLOBALS[ ltrim( $objname, '$' ) ] ) ) {
throw new Exception( sprintf( 'Variable \'%s\' is not set', $objname ) );
}
$obj = $GLOBALS[ ltrim( $objname, '$' ) ];
if ( is_object( $obj ) ) {
throw new Exception( sprintf( 'Objects (%s) don\'t support array access operators', $objname ) );
}
}
break;
}
$eval .= $token;
}
}
$last = count( $ts ) - 1;
if ( $last >= 2 &&
T_STRING === $ts[ $last - 0 ]['token'] &&
T_DOUBLE_COLON === $ts[ $last - 1 ]['token'] &&
T_STRING === $ts[ $last - 2 ]['token'] ) {
// $object has to exist and has to be a object.
$classname = $ts[ $last - 2 ]['value'];
if ( ! class_exists( $classname ) ) {
throw new Exception( sprintf( 'Class \'%s\' doesn\'t exist', $classname ) );
}
$constname = $ts[ $last - 0 ]['value'];
$c = new ReflectionClass( $classname );
if ( ! $c->hasConstant( $constname ) ) {
throw new Exception(
sprintf(
"Class '%s' doesn't have a constant named '%s'",
$classname,
$constname
)
);
}
} elseif ( 0 === $last &&
T_VARIABLE === $ts[ $last - 0 ]['token'] ) {
$varname = $ts[ $last - 0 ]['value'];
if ( ! isset( $GLOBALS[ ltrim( $varname, '$' ) ] ) ) {
throw new Exception( sprintf( 'Variable \'%s\' is not set', $varname ) );
}
}
$need_more = ( count( $braces ) > 0 ) || $open_comment;
if ( $need_more || ';' === $token ) {
$need_semicolon = 0;
}
if ( $need_return ) {
$eval = 'return ' . $eval;
}
if ( $need_more ) {
$swm_console['partial'] = $eval;
} else {
$swm_console['code'] = $eval;
}
update_option( "swm_console", $swm_console );
return $need_more;
}