Файловый менеджер - Редактировать - /home/ephorei/www/wp-includes/images/media/q2m9hb/ai-form-builder.tar
Назад
field-mapping.php 0000644 00000021116 15006153570 0007774 0 ustar 00 <?php /** * SureForms - AI Form Builder. * * @package sureforms * @since 0.0.8 */ namespace SRFM\Inc\AI_Form_Builder; use SRFM\Inc\Helper; use SRFM\Inc\Traits\Get_Instance; // Exit if accessed directly. if ( ! defined( 'ABSPATH' ) ) { exit; } /** * SureForms AI Form Builder Class. */ class Field_Mapping { use Get_Instance; /** * Generate Gutenberg Fields from AI data. * * @param \WP_REST_Request $request Full details about the request. * @return string */ public static function generate_gutenberg_fields_from_questions( $request ) { // Get params from request. $params = $request->get_params(); // check parama is empty or not and is an array and consist form_data key. if ( empty( $params ) || ! is_array( $params ) || ! isset( $params['form_data'] ) || 0 === count( $params['form_data'] ) ) { return ''; } // Get questions from form data. $form_data = $params['form_data']; if ( empty( $form_data ) || ! is_array( $form_data ) ) { return ''; } $form = $form_data['form']; if ( empty( $form ) || ! is_array( $form ) ) { return ''; } $form_fields = $form['formFields']; // if questions is empty then return empty string. if ( empty( $form_fields ) || ! is_array( $form ) ) { return ''; } // Initialize post content string. $post_content = ''; // Loop through questions. foreach ( $form_fields as $question ) { // Check if question is empty then continue to next question. if ( empty( $question ) || ! is_array( $question ) ) { return ''; } // Initialize common attributes. $common_attributes = [ 'block_id' => bin2hex( random_bytes( 4 ) ), // Generate random block_id. 'formId' => 0, // Set your formId here. ]; // Merge common attributes with question attributes. $merged_attributes = array_merge( $common_attributes, [ 'label' => sanitize_text_field( $question['label'] ), 'required' => filter_var( $question['required'], FILTER_VALIDATE_BOOLEAN ), 'help' => sanitize_text_field( $question['helpText'] ), ] ); // Determine field type based on fieldType. switch ( $question['fieldType'] ) { case 'input': case 'email': case 'number': case 'textarea': case 'dropdown': case 'checkbox': case 'address': case 'inline-button': case 'gdpr': case 'multi-choice': case 'url': case 'phone': // Handle specific attributes for certain fields. if ( 'dropdown' === $question['fieldType'] && ! empty( $question['fieldOptions'] ) && is_array( $question['fieldOptions'] ) && ! empty( $question['fieldOptions'][0]['label'] ) ) { $merged_attributes['options'] = $question['fieldOptions']; // remove icon from options for the dropdown field. foreach ( $merged_attributes['options'] as $key => $option ) { if ( ! empty( $merged_attributes['options'][ $key ]['icon'] ) ) { $merged_attributes['options'][ $key ]['icon'] = ''; } } } if ( 'multi-choice' === $question['fieldType'] ) { // Remove duplicate icons and clear icons if all are the same. $icons = array_column( $question['fieldOptions'], 'icon' ); $options = array_column( $question['fieldOptions'], 'optionTitle' ); $unique_icons = array_unique( $icons ); if ( count( $unique_icons ) === 1 || count( $options ) !== count( $icons ) ) { foreach ( $question['fieldOptions'] as &$option ) { $option['icon'] = ''; } } // Set options if they are valid. if ( ! empty( $question['fieldOptions'][0]['optionTitle'] ) ) { $merged_attributes['options'] = $question['fieldOptions']; } // Determine vertical layout based on icons. if ( ! empty( $merged_attributes['options'] ) ) { $merged_attributes['verticalLayout'] = array_reduce( $merged_attributes['options'], static fn( $carry, $option ) => $carry && ! empty( $option['icon'] ), true ); } // Set single selection if provided. if ( isset( $question['singleSelection'] ) ) { $merged_attributes['singleSelection'] = filter_var( $question['singleSelection'], FILTER_VALIDATE_BOOLEAN ); } // Set choiceWidth for options divisible by 3. if ( ! empty( $merged_attributes['options'] ) && count( $merged_attributes['options'] ) % 3 === 0 ) { $merged_attributes['choiceWidth'] = 33.33; } } if ( 'phone' === $question['fieldType'] ) { $merged_attributes['autoCountry'] = true; } $post_content .= '<!-- wp:srfm/' . $question['fieldType'] . ' ' . Helper::encode_json( $merged_attributes ) . ' /-->' . PHP_EOL; break; case 'slider': case 'page-break': case 'date-picker': case 'time-picker': case 'upload': case 'hidden': case 'rating': // If pro version is not active then do not add pro fields. if ( ! defined( 'SRFM_PRO_VER' ) ) { break; } // Handle specific attributes for certain pro fields. if ( 'date-picker' === $question['fieldType'] ) { $merged_attributes['dateFormat'] = ! empty( $question['dateFormat'] ) ? sanitize_text_field( $question['dateFormat'] ) : 'mm/dd/yy'; $merged_attributes['min'] = ! empty( $question['minDate'] ) ? sanitize_text_field( $question['minDate'] ) : ''; $merged_attributes['max'] = ! empty( $question['maxDate'] ) ? sanitize_text_field( $question['maxDate'] ) : ''; } if ( 'time-picker' === $question['fieldType'] ) { $merged_attributes['increment'] = ! empty( $question['increment'] ) ? filter_var( $question['increment'], FILTER_VALIDATE_INT ) : 30; $merged_attributes['showTwelveHourFormat'] = ! empty( $question['showTwelveHourFormat'] ) ? filter_var( $question['useTwelveHourFormat'], FILTER_VALIDATE_BOOLEAN ) : false; $merged_attributes['min'] = ! empty( $question['minTime'] ) ? sanitize_text_field( $question['minTime'] ) : ''; $merged_attributes['max'] = ! empty( $question['maxTime'] ) ? sanitize_text_field( $question['maxTime'] ) : ''; } if ( 'rating' === $question['fieldType'] ) { $merged_attributes['iconShape'] = ! empty( $question['iconShape'] ) ? sanitize_text_field( $question['iconShape'] ) : 'star'; $merged_attributes['showText'] = ! empty( $question['showTooltip'] ) ? filter_var( $question['showTooltip'], FILTER_VALIDATE_BOOLEAN ) : false; $merged_attributes['defaultRating'] = ! empty( $question['defaultRating'] ) ? filter_var( $question['defaultRating'], FILTER_VALIDATE_INT ) : 0; if ( ! empty( $merged_attributes['showText'] ) ) { foreach ( $question['tooltipValues'] as $tooltips ) { $i = 0; foreach ( $tooltips as $value ) { $merged_attributes['ratingText'][ $i ] = ! empty( $value ) ? sanitize_text_field( $value ) : ''; $i++; } } } } if ( 'upload' === $question['fieldType'] ) { if ( ! empty( $question['allowedTypes'] ) ) { $allowed_types = str_replace( '.', '', $question['allowedTypes'] ); $allowed_types = explode( ',', $allowed_types ); $types_array = array_map( static function( $type ) { return [ 'value' => trim( $type ), 'label' => trim( $type ), ]; }, $allowed_types ); $merged_attributes['allowedFormats'] = $types_array; } else { $merged_attributes['allowedFormats'] = [ [ 'value' => 'jpg', 'label' => 'jpg', ], [ 'value' => 'jpeg', 'label' => 'jpeg', ], [ 'value' => 'gif', 'label' => 'gif', ], [ 'value' => 'png', 'label' => 'png', ], [ 'value' => 'pdf', 'label' => 'pdf', ], ]; } $merged_attributes['fileSizeLimit'] = ! empty( $question['uploadSize'] ) ? filter_var( $question['uploadSize'], FILTER_VALIDATE_INT ) : 10; $merged_attributes['multiple'] = ! empty( $question['multiUpload'] ) ? filter_var( $question['multiUpload'], FILTER_VALIDATE_BOOLEAN ) : false; $merged_attributes['maxFiles'] = ! empty( $question['multiFilesNumber'] ) ? filter_var( $question['multiFilesNumber'], FILTER_VALIDATE_INT ) : 2; } $post_content .= '<!-- wp:srfm/' . $question['fieldType'] . ' ' . Helper::encode_json( $merged_attributes ) . ' /-->' . PHP_EOL; break; default: // Unsupported field type - fallback to input. $post_content .= '<!-- wp:srfm/input ' . Helper::encode_json( $merged_attributes ) . ' /-->' . PHP_EOL; } } return $post_content; } } ai-auth.php 0000644 00000011260 15006153570 0006607 0 ustar 00 <?php /** * SureForms - AI Auth. * * @package sureforms * @since 0.0.8 */ namespace SRFM\Inc\AI_Form_Builder; use SRFM\Inc\Helper; use SRFM\Inc\Traits\Get_Instance; // Exit if accessed directly. if ( ! defined( 'ABSPATH' ) ) { exit; } /** * SureForms AI Form Builder Class. */ class AI_Auth { use Get_Instance; /** * The key for encryption and decryption. * * @since 0.0.8 * @var string */ private $key = ''; /** * Initiates the auth process. * * @param \WP_REST_Request $request The request object. * @since 0.0.8 * @return void */ public function get_auth_url( $request ) { $nonce = Helper::get_string_value( $request->get_header( 'X-WP-Nonce' ) ); if ( ! wp_verify_nonce( sanitize_text_field( $nonce ), 'wp_rest' ) ) { wp_send_json_error( __( 'Nonce verification failed.', 'sureforms' ) ); } // Generate a random key of 16 characters. $this->key = wp_generate_password( 16, false ); // Prepare the token data. $token_data = [ 'redirect-back' => site_url() . '/wp-admin/admin.php?page=add-new-form&method=ai', 'key' => $this->key, 'site-url' => site_url(), 'nonce' => wp_create_nonce( 'ai_auth_nonce' ), ]; $encoded_token_data = wp_json_encode( $token_data ); if ( empty( $encoded_token_data ) ) { wp_send_json_error( [ 'message' => __( 'Failed to encode the token data.', 'sureforms' ) ] ); } // Send the token data to the frontend for redirection. wp_send_json_success( SRFM_BILLING_PORTAL . 'auth/?token=' . base64_encode( $encoded_token_data ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode } /** * Handles the access key. * * @param \WP_REST_Request $request The request object. * @since 0.0.8 * @return void */ public function handle_access_key( $request ) { $nonce = Helper::get_string_value( $request->get_header( 'X-WP-Nonce' ) ); if ( ! wp_verify_nonce( sanitize_text_field( $nonce ), 'wp_rest' ) ) { wp_send_json_error( __( 'Nonce verification failed.', 'sureforms' ) ); } // get body data. $body = json_decode( $request->get_body(), true ); if ( empty( $body ) ) { wp_send_json_error( [ 'message' => __( 'Error processing Access Key.', 'sureforms' ) ] ); } // get access key. $access_key = is_array( $body ) && ! empty( $body['accessKey'] ) ? Helper::get_string_value( $body['accessKey'] ) : ''; // decrypt the access key. if ( ! empty( $access_key ) ) { $this->decrypt_access_key( $access_key, $this->key ); } else { wp_send_json_error( [ 'message' => __( 'No access key provided.', 'sureforms' ) ] ); } } /** * Decrypts a string using OpenSSL decryption. * * @param string $data The data to decrypt. * @param string $key The encryption key. * @param string $method The encryption method (e.g., AES-256-CBC). * @since 0.0.8 * @return string|false The decrypted string or false on failure. */ public function decrypt_access_key( $data, $key, $method = 'AES-256-CBC' ) { // Decode the data and split IV and encrypted data. $decoded_data = base64_decode( $data ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode // if the data is not base64 encoded then return false. if ( empty( $decoded_data ) ) { return false; } // split the key and encrypted data. [$key, $encrypted] = explode( '::', $decoded_data, 2 ); // Decrypt the data using the key. $decrypted = openssl_decrypt( $encrypted, $method, $key, 0, $key ); // if the decryption returns false then send error. if ( empty( $decrypted ) ) { wp_send_json_error( [ 'message' => __( 'Failed to decrypt the access key.', 'sureforms' ) ] ); } // json decode the decrypted data. $decrypted_data_array = json_decode( $decrypted, true ); if ( ! is_array( $decrypted_data_array ) || empty( $decrypted_data_array ) ) { wp_send_json_error( [ 'message' => __( 'Failed to json decode the decrypted data.', 'sureforms' ) ] ); } // verify the nonce that comes in $encrypted_email_array. if ( ! empty( $decrypted_data_array['nonce'] ) && ! wp_verify_nonce( $decrypted_data_array['nonce'], 'ai_auth_nonce' ) ) { wp_send_json_error( [ 'message' => __( 'Nonce verification failed.', 'sureforms' ) ] ); } // check if the user email is present in the decrypted data. if ( empty( $decrypted_data_array['user_email'] ) ) { wp_send_json_error( [ 'message' => __( 'No user email found in the decrypted data.', 'sureforms' ) ] ); } // remove the nonce from the decrypted data before saving it to the options. unset( $decrypted_data_array['nonce'] ); // save the user email to the options. update_option( 'srfm_ai_auth_user_email', $decrypted_data_array ); wp_send_json_success(); } } ai-helper.php 0000644 00000022573 15006153570 0007136 0 ustar 00 <?php /** * SureForms AI Form Builder - Helper. * * This file contains the helper functions of SureForms AI Form Builder. * Helpers are functions that are used throughout the library. * * @package sureforms * @since 0.0.8 */ namespace SRFM\Inc\AI_Form_Builder; use SRFM\Inc\Traits\Get_Instance; use SRFM_Pro\Admin\Licensing; // Exit if accessed directly. if ( ! defined( 'ABSPATH' ) ) { exit; } /** * The Helper Class. */ class AI_Helper { use Get_Instance; /** * Get the SureForms AI Response from the SureForms Credit Server. * * @param array<mixed> $body The data to be passed as the request body, if any. * @param array<mixed> $extra_args Extra arguments to be passed to the request, if any. * @since 0.0.8 * @return array<array<array<array<mixed>>>|string>|mixed The SureForms AI Response. */ public static function get_chat_completions_response( $body = [], $extra_args = [] ) { // Set the API URL. $api_url = SRFM_AI_MIDDLEWARE . 'generate/form'; $api_args = [ 'headers' => [ 'X-Token' => base64_encode( self::get_user_token() ), // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode -- This is not for obfuscation. 'Content-Type' => 'application/json', 'Referer' => site_url(), ], 'timeout' => 90, // phpcs:ignore WordPressVIPMinimum.Performance.RemoteRequestTimeout.timeout_timeout -- 90 seconds is required sometime for open ai responses ]; // If the data array was passed, add it to the args. if ( ! empty( $body ) && is_array( $body ) ) { $api_args['body'] = wp_json_encode( $body ); } // If there are any extra arguments, then we can overwrite the required arguments. if ( ! empty( $extra_args ) && is_array( $extra_args ) ) { $api_args = array_merge( $api_args, $extra_args ); } // Get the response from the endpoint. $response = wp_remote_post( $api_url, $api_args ); // If the response was an error, or not a 200 status code, then abandon ship. if ( is_wp_error( $response ) || empty( $response['response'] ) || 200 !== wp_remote_retrieve_response_code( $response ) ) { return self::get_error_message( $response ); } // Get the response body. $response_body = wp_remote_retrieve_body( $response ); // If the response body is not a JSON, then abandon ship. if ( empty( $response_body ) || ! json_decode( $response_body ) ) { return [ 'error' => __( 'The SureForms AI Middleware encountered an error.', 'sureforms' ), ]; } // Return the response body. return json_decode( $response_body, true ); } /** * Get the SureForms Token from the SureForms AI Settings. * * @since 0.0.8 * @return array<mixed>|void The SureForms Token. */ public static function get_current_usage_details() { $current_usage_details = []; // Get the response from the endpoint. $response = self::get_usage_response(); // check if response is an array if not then send error. if ( ! is_array( $response ) ) { wp_send_json_error( [ 'message' => __( 'Unable to get usage response.', 'sureforms' ) ] ); } // If the response is not an error, then use it - else create an error response array. if ( empty( $response['error'] ) && is_array( $response ) ) { $current_usage_details = $response; if ( empty( $current_usage_details['status'] ) ) { $current_usage_details['status'] = 'ok'; } } else { $current_usage_details['status'] = 'error'; if ( ! empty( $response['error'] ) ) { $current_usage_details['error'] = $response['error']; } } return $current_usage_details; } /** * Get a response from the SureForms API server. * * @since 0.0.8 * @return array<mixed>|mixed The SureForms API Response. */ public static function get_usage_response() { // Set the API URL. $api_url = SRFM_AI_MIDDLEWARE . 'usage'; // Get the response from the endpoint. $response = wp_remote_post( $api_url, [ 'headers' => [ 'X-Token' => base64_encode( self::get_user_token() ), // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode -- This is not for obfuscation. 'Content-Type' => 'application/json', 'Referer' => site_url(), ], 'timeout' => 30, // phpcs:ignore WordPressVIPMinimum.Performance.RemoteRequestTimeout.timeout_timeout -- 30 seconds is required sometime for the SureForms API response ] ); // If the response was an error, or not a 200 status code, then abandon ship. if ( is_wp_error( $response ) || empty( $response['response'] ) || 200 !== wp_remote_retrieve_response_code( $response ) ) { return self::get_error_message( $response ); } // Get the response body. $response_body = wp_remote_retrieve_body( $response ); // If the response body is not a JSON, then abandon ship. if ( empty( $response_body ) || ! json_decode( $response_body ) ) { return [ 'error' => __( 'The SureForms API server encountered an error.', 'sureforms' ), ]; } // Return the response body. return json_decode( $response_body, true ); } /** * Get the Error Message. * * @param array<string,mixed>|array<int|string,mixed>|\WP_Error $response The response from the SureForms API server. * @since 0.0.10 * @return array<string, mixed> The Error Message. */ public static function get_error_message( $response ) { $errors = $response->errors ?? []; if ( empty( $errors ) && is_array( $response ) && isset( $response['body'] ) && is_string( $response['body'] ) ) { $errors = json_decode( $response['body'], true ); $error_key = is_array( $errors ) && isset( $errors['code'] ) ? $errors['code'] : ''; } else { $error_key = array_key_first( $errors ); if ( empty( $errors[ $error_key ] ) ) { $message = __( 'An unknown error occurred.', 'sureforms' ); } } // Error Codes with Messages. switch ( $error_key ) { case 'http_request_failed': $title = __( 'HTTP Request Failed', 'sureforms' ); $message = __( 'An error occurred while trying to connect to the SureForms API server. Please check your connection', 'sureforms' ); break; case 'license_verification_failed': $title = __( 'License Verification Failed', 'sureforms' ); $message = __( 'An error occurred while trying to verify your license. Please check your license key', 'sureforms' ); break; case 'user_verification_failed': $title = __( 'User Verification Failed', 'sureforms' ); $message = __( 'An error occurred while trying to verify your email. Please check your email you have used to log in/ sign up on billing.sureforms.com.', 'sureforms' ); break; case 'referer_mismatch': $title = __( 'Referer Mismatch', 'sureforms' ); $message = __( 'An error occurred while trying to verify your referer. Please check your referer.', 'sureforms' ); break; case 'invalid_token': $title = __( 'Invalid Website URL', 'sureforms' ); $message = __( 'AI Form Builder does not work on localhost. Please try on a live website.', 'sureforms' ); break; case 'domain_verification_failed': $title = __( 'Domain Verification Failed', 'sureforms' ); $message = __( 'Domain Verification Failed on current site. Please try again on any another website.', 'sureforms' ); break; default: $title = __( 'Unknown Error', 'sureforms' ); $message = __( 'An unknown error occurred.', 'sureforms' ); } return [ 'code' => $error_key, 'title' => $title, 'message' => $message, ]; } /** * Check if the SureForms Pro license is active. * * @since 0.0.10 * @return bool|string True if the SureForms Pro license is active, false otherwise. */ public static function is_pro_license_active() { $licensing = self::get_licensing_instance(); if ( ! $licensing || ! method_exists( $licensing, 'is_license_active' ) ) { return ''; } // Check if the SureForms Pro license is active. return $licensing->is_license_active(); } /** * Get the User Token. * * @since 0.0.8 * @return string The User Token. */ private static function get_user_token() { // if the license is active then use the license key as the token. if ( defined( 'SRFM_PRO_VER' ) ) { $license_key = self::get_license_key(); if ( ! empty( $license_key ) ) { return $license_key; } } $user_email = get_option( 'srfm_ai_auth_user_email' ); // if the license is not active then use the user email/site url as the token. return ! empty( $user_email ) && is_array( $user_email ) ? $user_email['user_email'] : site_url(); } /** * Get the Licensing Instance. * * @since 0.0.10 * @return object|null The Licensing Instance. */ private static function get_licensing_instance() { if ( ! class_exists( 'SRFM_Pro\Admin\Licensing' ) ) { return null; } return Licensing::get_instance(); } /** * Get the SureForms Pro License Key. * * @since 0.0.10 * @return string The SureForms Pro License Key. */ private static function get_license_key() { $licensing = self::get_licensing_instance(); if ( ! $licensing || ! method_exists( $licensing, 'licensing_setup' ) || ! method_exists( $licensing->licensing_setup(), 'settings' ) ) { return ''; } // Check if the SureForms Pro license is active. $is_license_active = self::is_pro_license_active(); // If the license is active, get the license key. $license_setup = $licensing->licensing_setup(); return ! empty( $is_license_active ) && is_object( $license_setup ) && method_exists( $license_setup, 'settings' ) ? $license_setup->settings()->license_key : ''; } } ai-form-builder.php 0000644 00000005567 15006153570 0010252 0 ustar 00 <?php /** * SureForms - AI Form Builder. * * @package sureforms * @since 0.0.8 */ namespace SRFM\Inc\AI_Form_Builder; use SRFM\Inc\Helper; use SRFM\Inc\Traits\Get_Instance; // Exit if accessed directly. if ( ! defined( 'ABSPATH' ) ) { exit; } /** * SureForms AI Form Builder Class. */ class AI_Form_Builder { use Get_Instance; /** * Fetches ai data from the middleware server * * @param \WP_REST_Request $request request object. * @since 0.0.8 * @return void */ public function generate_ai_form( $request ) { // Get the params. $params = $request->get_params(); // If the message array doesn't exist, abandon ship. if ( empty( $params['message_array'] ) || ! is_array( $params['message_array'] ) ) { wp_send_json_error( [ 'message' => __( 'The message array was not supplied', 'sureforms' ) ] ); } // Set the token count to 0, and create messages array. $messages = []; // Start with the last message - going upwards until the token count hits 2000. foreach ( array_reverse( $params['message_array'] ) as $current_message ) { // If the message content doesn't exist, skip it. if ( empty( $current_message['content'] ) ) { continue; } // Add the message to the start of the messages to send to the SCS Middleware. array_unshift( $messages, $current_message ); } // Get the response from the endpoint. $response = AI_Helper::get_chat_completions_response( array_merge( [ 'query' => $messages[0]['content'], ], defined( 'SRFM_AI_SYSTEM_PROMPT_ARGS' ) ? Helper::get_array_value( SRFM_AI_SYSTEM_PROMPT_ARGS ) : [] ) ); // check if response is an array if not then send error. if ( ! is_array( $response ) ) { wp_send_json_error( [ 'message' => __( 'The SureForms AI Middleware encountered an error.', 'sureforms' ) ] ); } if ( ! empty( $response['error'] ) ) { // If the response has an error, handle it and report it back. $message = ''; if ( ! empty( $response['error']['message'] ) ) { // If any error message received from OpenAI. $message = $response['error']['message']; } elseif ( is_string( $response['error'] ) ) { // If any error message received from server. if ( ! empty( $response['code'] && is_string( $response['code'] ) ) ) { $message = __( 'The SureForms AI Middleware encountered an error.', 'sureforms' ); } $message = ! empty( $message ) ? $message : $response['error']; } wp_send_json_error( [ 'message' => $message ] ); } elseif ( is_array( $response['form'] ) && ! empty( $response['form']['formTitle'] ) && is_array( $response['form']['formFields'] ) && ! empty( $response['form']['formFields'] ) ) { // If the message was sent successfully, send it successfully. wp_send_json_success( $response ); } else { // If you've reached here, then something has definitely gone amuck. Abandon ship. wp_send_json_error( $response ); }//end if } }
| ver. 1.4 |
Github
|
.
| PHP 8.0.30 | Генерация страницы: 0 |
proxy
|
phpinfo
|
Настройка