<?php

/**
 * Code Manager main class
 *
 * @package Code_Manager
 */
namespace Code_Manager;

require_once plugin_dir_path( dirname( __FILE__ ) ) . 'CM_Tools.php';
// Load Code Manager tools.
/**
 * Class Code_Manager
 *
 * Add plugin actions and runs the code saved in the code manager table.
 *
 * @author  Peter Schulz
 * @since   1.0.0
 */
class Code_Manager {
    const CM_MESSAGE = 'code_manager_alert_off';

    const PHP_OPEN = '<?php';

    const PHP_CLOSE = '?>';

    const CSS_OPEN = '<style>';

    const CSS_CLOSE = '</style>';

    const JS_OPEN = '<script>';

    const JS_CLOSE = '</script>';

    /**
     * Add Code Manager specific actions
     *
     * @param Object $loader Plugin loader.
     * @since   1.0.0
     */
    public function add_actions( $loader ) {
        $code_manager_model = new Code_Manager_Model();
        if ( is_admin() ) {
            // Admin actions.
            $loader->add_action( 'admin_action_code_manager_export', Code_Manager_Export::class, 'export' );
            $loader->add_action( 'wp_ajax_code_manager_export', Code_Manager_Export::class, 'export_ajax' );
            $loader->add_action( 'wp_ajax_nopriv_code_manager_export', Code_Manager_Export::class, 'export_ajax' );
            $loader->add_action( 'wp_ajax_code_manager_update_code', $code_manager_model, 'update_code' );
            $loader->add_action( 'wp_ajax_code_manager_activate_code', $code_manager_model, 'activate_code' );
            $loader->add_action( 'wp_ajax_code_manager_activate_code_preview', $code_manager_model, 'activate_code_preview' );
            $loader->add_action( 'wp_ajax_code_manager_deactivate_code_preview', $code_manager_model, 'deactivate_code_preview' );
            $loader->add_action( 'wp_ajax_code_manager_reset_preview', $code_manager_model, 'reset_preview' );
            $loader->add_action( 'wp_ajax_code_manager_get_code_list', $code_manager_model, 'get_code_list' );
            $loader->add_action( 'wp_ajax_code_manager_code_name_exists', $code_manager_model, 'code_name_exists' );
            $loader->add_action( 'wp_ajax_code_manager_is_code_preview_enabled', $code_manager_model, 'is_code_preview_enabled' );
            $loader->add_action( 'wp_ajax_code_manager_get_code', $code_manager_model, 'get_code' );
            $loader->add_action( 'wp_ajax_nopriv_code_manager_get_code', $code_manager_model, 'get_code' );
            $loader->add_action( 'wp_ajax_code_manager_alert_off', Code_Manager::class, 'alert_off' );
        }
    }

    /**
     * Adds activated as well as preview enabled code
     */
    public function add_active_codes() {
    }

    /**
     * Find and add all CSS
     *
     * @return void
     */
    public function add_styles() {
    }

    /**
     * Add one specific CSS block
     *
     * @param integer $code_id Code ID.
     * @return void
     */
    protected function add_style( $code_id ) {
    }

    public function add_scripts() {
    }

    protected function add_script( $code_id ) {
    }

    /**
     * Run shortcode
     *
     * @since   1.0.0
     *
     * @param array $atts Shortcode arguments.
     * @var array
     */
    public function add_shortcode( $atts ) {
        if ( self::code_manager_disabled() ) {
            // Code manager disabled.
            return '';
        }
        global $pagenow;
        if ( 'post.php' === $pagenow || 'edit.php' === $pagenow || 'post-new.php' === $pagenow ) {
            // Prevent errors on execution if shortcode is shown in classic editor.
            return '';
        }
        if ( isset( $_SERVER['CONTENT_TYPE'] ) && 'application/json' === $_SERVER['CONTENT_TYPE'] ) {
            // Prevent errors on execution if shortcode is shown in Gutenberg editor.
            return null;
        }
        global $wpda_shortcode_args;
        $wpda_shortcode_args = $atts;
        // Allow user to define and use custom parameters.
        $atts = array_change_key_case( (array) $atts, CASE_LOWER );
        $wp_atts = shortcode_atts( array(
            'id'   => '',
            'name' => '',
        ), $atts );
        if ( '' === $wp_atts['id'] && '' === $wp_atts['name'] ) {
            return '';
        }
        ob_start();
        $ids = explode( ',', $wp_atts['id'] );
        foreach ( $ids as $id ) {
            $this->run_shortcode_id( $id );
        }
        $names = explode( ',', $wp_atts['name'] );
        foreach ( $names as $name ) {
            $this->run_shortcode_name( $name );
        }
        $content = ob_get_contents();
        ob_end_clean();
        return $content;
    }

    /**
     * Run shortcode from code id
     *
     * @param integer $id Code ID.
     * @return void
     */
    protected function run_shortcode_id( $id ) {
        if ( '' !== $id ) {
            $code_manager_model = new Code_Manager_Model();
            $code_row = $code_manager_model::dml_query( $id );
            if ( 1 === count( $code_row ) ) {
                if ( '1' === $code_row[0]['code_enabled'] || Code_Manager_Preview::is_code_id_preview_enabled( $id ) ) {
                    $this->run_shortcode( $code_row[0]['code_type'], $code_row[0]['code'] );
                }
            }
        }
    }

    /**
     * Run shortcode from code name
     *
     * @param string $name Code name.
     * @return void
     */
    protected function run_shortcode_name( $name ) {
        if ( '' !== $name ) {
            $code_manager_model = new Code_Manager_Model();
            $code_row = $code_manager_model::dml_query_by_name( $name );
            if ( 1 === count( $code_row ) ) {
                if ( '1' === $code_row[0]['code_enabled'] || Code_Manager_Preview::is_code_id_preview_enabled( $code_row[0]['code_id'] ) ) {
                    $this->run_shortcode( $code_row[0]['code_type'], $code_row[0]['code'] );
                }
            }
        }
    }

    /**
     * Adds code de pending on the code type
     *
     * @since   1.0.0
     *
     * @param string $code_type Code type (shortcodes only).
     * @param string $code The code (PHP, JS, CSS or HTML).
     */
    protected function run_shortcode( $code_type, $code ) {
        if ( strpos( $code_type, 'html' ) !== false ) {
            echo wp_unslash( $code );
            // phpcs:ignore WordPress.Security.EscapeOutput
        } elseif ( strpos( $code_type, 'css' ) !== false ) {
            echo self::CSS_OPEN . wp_unslash( $code ) . self::CSS_CLOSE;
            // phpcs:ignore WordPress.Security.EscapeOutput
        } elseif ( strpos( $code_type, 'javascript' ) !== false ) {
            echo self::JS_OPEN . wp_unslash( $code ) . self::JS_CLOSE;
            // phpcs:ignore WordPress.Security.EscapeOutput
        } elseif ( 'php shortcode' === $code_type ) {
            $this->add_php_code( $code );
        }
    }

    /**
     * Run shortcode from back-end code block using code id
     *
     * @param integer $id Code ID.
     * @return void
     */
    public function run_shortcode_id_from_anywhere( $id ) {
        $this->run_shortcode_id( $id );
    }

    /**
     * Run shortcode from back-end code block using code name
     *
     * @param string $name Code name.
     * @return void
     */
    public function run_shortcode_name_from_anywhere( $name ) {
        $this->run_shortcode_name( $name );
    }

    /**
     * Adds PHP code
     *
     * @since   1.0.0
     *
     * @param string $php_code PHP code to be added.
     */
    protected function add_php_code( $php_code ) {
        if ( Code_Manager::is_code_manager_page() ) {
            // Do not execute any code on Code Manager pages!!!
            // This is an admins rescue in case code fails and locks a server.
        } else {
            $php_code_valid = true;
            $php_code_functions = $this->get_classes_and_functions( $php_code );
            foreach ( $php_code_functions['functions'] as $php_code_function ) {
                if ( function_exists( $php_code_function ) ) {
                    var_dump( "ERROR: Function {$php_code_function} is already declared" );
                    $php_code_valid = false;
                }
            }
            foreach ( $php_code_functions['classes'] as $php_code_class ) {
                if ( class_exists( $php_code_class ) ) {
                    var_dump( "ERROR: Class {$php_code_class} is already declared" );
                    $php_code_valid = false;
                }
            }
            if ( $php_code_valid ) {
                try {
                    eval( $this->strip_code( $php_code ) );
                } catch ( \ParseError $e ) {
                    echo $e->getMessage();
                } catch ( \Throwable $e ) {
                    echo $e->getMessage();
                }
            }
        }
    }

    protected function get_classes_and_functions( $php_code ) {
        $tokens = token_get_all( $php_code );
        $class_names = array();
        $has_class_name = false;
        $parsing_class = false;
        $function_names = array();
        $parsing_function = false;
        $open_braces = 0;
        foreach ( $tokens as $token ) {
            $token_start = $token[0];
            if ( T_CLASS === $token_start ) {
                $parsing_class = true;
            } elseif ( T_FUNCTION === $token_start ) {
                if ( !$parsing_class ) {
                    $parsing_function = true;
                }
            } elseif ( T_STRING === $token_start ) {
                if ( $parsing_function ) {
                    $parsing_function = false;
                    $function_names[] = $token[1];
                } elseif ( $parsing_class && !$has_class_name ) {
                    $class_names[] = $token[1];
                    $has_class_name = true;
                }
            } elseif ( '(' === $token_start || ';' === $token_start ) {
                $parsing_function = false;
            } elseif ( '{' === $token_start ) {
                if ( $parsing_class ) {
                    $open_braces++;
                }
            } elseif ( '}' === $token_start ) {
                if ( $parsing_class ) {
                    $open_braces--;
                    if ( 0 === $open_braces ) {
                        $parsing_class = false;
                    }
                }
            }
        }
        return array(
            'classes'   => $class_names,
            'functions' => $function_names,
        );
    }

    /**
     * Remove PHP opening and closing tags (when found) from given code
     *
     * @since   1.0.0
     *
     * @param string $php_code PHP source code.
     *
     * @return string PHP code without PHP opening and closing tags
     */
    protected function strip_code( $php_code ) {
        $php_code = rtrim( ltrim( $php_code ) );
        if ( self::PHP_OPEN === strtolower( substr( $php_code, 0, 5 ) ) ) {
            $php_code = substr( $php_code, 5 );
        }
        if ( self::PHP_CLOSE === substr( $php_code, strlen( $php_code ) - 2 ) ) {
            $php_code = substr( $php_code, 0, strlen( $php_code ) - 2 );
        }
        return $php_code;
    }

    /**
     * Checks if Code Manager is disabled
     *
     * (1) Disabled in settings page
     * (2) Disabled in config file
     *
     * @since   1.0.0
     *
     * @return bool TRUE - Code Manager is disabled
     */
    public static function code_manager_disabled() {
        $plugin_code_execution = get_option( 'code_manager_plugin_code_execution' );
        if ( false === $plugin_code_execution ) {
            $plugin_code_execution = 'on';
        }
        return 'on' !== $plugin_code_execution || defined( 'CODE_MANAGER_DISABLED' ) && CODE_MANAGER_DISABLED;
    }

    /**
     * Returns true if user is on a Code Manager back-end page
     *
     * @return bool
     */
    public static function is_code_manager_page() {
        return is_admin() && isset( $_REQUEST['page'] ) && (\Code_Manager_Admin::PAGE_MAIN === $_REQUEST['page'] || \Code_Manager_Admin::PAGE_TAB === $_REQUEST['page'] || \Code_Manager_Admin::PAGE_SETTINGS === $_REQUEST['page'] || 'code_manager_post' === $_REQUEST['page']);
    }

    /**
     * Get current user login
     *
     * @return string
     */
    public static function get_current_user_login() {
        global $current_user;
        if ( isset( $current_user->user_login ) ) {
            return $current_user->user_login;
        } else {
            $wp_user = wp_get_current_user();
            if ( isset( $wp_user->data->user_login ) ) {
                return $wp_user->data->user_login;
            } else {
                return 'anonymous';
            }
        }
    }

    /**
     * Get current user id
     *
     * @return string
     */
    public static function get_current_user_id() {
        global $current_user;
        if ( isset( $current_user->ID ) ) {
            return $current_user->ID;
        } else {
            $wp_user = wp_get_current_user();
            if ( isset( $wp_user->data->ID ) ) {
                return $wp_user->data->ID;
            } else {
                return -1;
            }
        }
    }

    public static function get_cm_message() {
        return get_user_meta( self::get_current_user_id(), self::CM_MESSAGE, true );
    }

    public static function alert_off() {
        if ( is_user_logged_in() && isset( $_REQUEST['wpnonce'] ) ) {
            // Check if action is allowed.
            $wpnonce = sanitize_text_field( wp_unslash( $_REQUEST['wpnonce'] ) );
            // input var okay.
            if ( wp_verify_nonce( $wpnonce, 'code-manager-' . Code_manager::get_current_user_login() ) ) {
                // Turn off alarm.
                update_user_meta( self::get_current_user_id(), self::CM_MESSAGE, 'hide' );
            }
        }
        wp_die();
    }

}
