first commit
This commit is contained in:
@@ -0,0 +1,152 @@
|
||||
<?php
|
||||
namespace Elementor\Modules\KitElementsDefaults\Data;
|
||||
|
||||
use Elementor\Core\Frontend\Performance;
|
||||
use Elementor\Modules\KitElementsDefaults\Module;
|
||||
use Elementor\Modules\KitElementsDefaults\Utils\Settings_Sanitizer;
|
||||
use Elementor\Plugin;
|
||||
use Elementor\Data\V2\Base\Exceptions\Error_404;
|
||||
use Elementor\Data\V2\Base\Controller as Base_Controller;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Controller extends Base_Controller {
|
||||
|
||||
public function get_name() {
|
||||
return 'kit-elements-defaults';
|
||||
}
|
||||
|
||||
public function register_endpoints() {
|
||||
$this->index_endpoint->register_item_route(\WP_REST_Server::EDITABLE, [
|
||||
'id_arg_name' => 'type',
|
||||
'id_arg_type_regex' => '[\w\-\_]+',
|
||||
'type' => [
|
||||
'type' => 'string',
|
||||
'description' => 'The type of the element.',
|
||||
'required' => true,
|
||||
'validate_callback' => function( $type ) {
|
||||
return $this->validate_type( $type );
|
||||
},
|
||||
],
|
||||
'settings' => [
|
||||
'description' => 'All the default values for the requested type',
|
||||
'required' => true,
|
||||
'type' => 'object',
|
||||
'validate_callback' => function( $settings ) {
|
||||
return is_array( $settings );
|
||||
},
|
||||
'sanitize_callback' => function( $settings, \WP_REST_Request $request ) {
|
||||
Performance::set_use_style_controls( true );
|
||||
|
||||
$sanitizer = new Settings_Sanitizer(
|
||||
Plugin::$instance->elements_manager,
|
||||
array_keys( Plugin::$instance->widgets_manager->get_widget_types() )
|
||||
);
|
||||
|
||||
$sanitized_data = $sanitizer
|
||||
->for( $request->get_param( 'type' ) )
|
||||
->using( $settings )
|
||||
->remove_invalid_settings()
|
||||
->kses_deep()
|
||||
->get();
|
||||
|
||||
Performance::set_use_style_controls( false );
|
||||
|
||||
return $sanitized_data;
|
||||
},
|
||||
],
|
||||
] );
|
||||
|
||||
$this->index_endpoint->register_item_route(\WP_REST_Server::DELETABLE, [
|
||||
'id_arg_name' => 'type',
|
||||
'id_arg_type_regex' => '[\w\-\_]+',
|
||||
'type' => [
|
||||
'type' => 'string',
|
||||
'description' => 'The type of the element.',
|
||||
'required' => true,
|
||||
'validate_callback' => function( $type ) {
|
||||
return $this->validate_type( $type );
|
||||
},
|
||||
],
|
||||
] );
|
||||
}
|
||||
|
||||
public function get_collection_params() {
|
||||
return [];
|
||||
}
|
||||
|
||||
public function get_items( $request ) {
|
||||
$this->validate_kit();
|
||||
|
||||
$kit = Plugin::$instance->kits_manager->get_active_kit();
|
||||
|
||||
return (object) $kit->get_json_meta( Module::META_KEY );
|
||||
}
|
||||
|
||||
public function update_item( $request ) {
|
||||
$this->validate_kit();
|
||||
|
||||
$kit = Plugin::$instance->kits_manager->get_active_kit();
|
||||
|
||||
$data = $kit->get_json_meta( Module::META_KEY );
|
||||
|
||||
$data[ $request->get_param( 'type' ) ] = $request->get_param( 'settings' );
|
||||
|
||||
$kit->update_json_meta( Module::META_KEY, $data );
|
||||
|
||||
return (object) [];
|
||||
}
|
||||
|
||||
public function delete_item( $request ) {
|
||||
$this->validate_kit();
|
||||
|
||||
$kit = Plugin::$instance->kits_manager->get_active_kit();
|
||||
|
||||
$data = $kit->get_json_meta( Module::META_KEY );
|
||||
|
||||
unset( $data[ $request->get_param( 'type' ) ] );
|
||||
|
||||
$kit->update_json_meta( Module::META_KEY, $data );
|
||||
|
||||
return (object) [];
|
||||
}
|
||||
|
||||
private function validate_kit() {
|
||||
$kit = Plugin::$instance->kits_manager->get_active_kit();
|
||||
$is_valid_kit = $kit && $kit->get_main_id();
|
||||
|
||||
if ( ! $is_valid_kit ) {
|
||||
throw new Error_404( 'Kit doesn\'t exist.' );
|
||||
}
|
||||
}
|
||||
|
||||
private function validate_type( $param ) {
|
||||
$element_types = array_keys( Plugin::$instance->elements_manager->get_element_types() );
|
||||
$widget_types = array_keys( Plugin::$instance->widgets_manager->get_widget_types() );
|
||||
|
||||
return in_array(
|
||||
$param,
|
||||
array_merge( $element_types, $widget_types ),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
public function get_items_permissions_check( $request ) {
|
||||
return current_user_can( 'edit_posts' );
|
||||
}
|
||||
|
||||
// TODO: Should be removed once the infra will support it.
|
||||
public function get_item_permissions_check( $request ) {
|
||||
return $this->get_items_permissions_check( $request );
|
||||
}
|
||||
|
||||
public function update_item_permissions_check( $request ) {
|
||||
return current_user_can( 'manage_options' );
|
||||
}
|
||||
|
||||
public function delete_item_permissions_check( $request ) {
|
||||
return current_user_can( 'manage_options' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
namespace Elementor\Modules\KitElementsDefaults\ImportExport;
|
||||
|
||||
use Elementor\App\Modules\ImportExport\Processes\Export;
|
||||
use Elementor\App\Modules\ImportExport\Processes\Import;
|
||||
use Elementor\Modules\KitElementsDefaults\ImportExport\Runners\Export as Export_Runner;
|
||||
use Elementor\Modules\KitElementsDefaults\ImportExport\Runners\Import as Import_Runner;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Import_Export {
|
||||
const FILE_NAME = 'kit-elements-defaults';
|
||||
|
||||
public function register() {
|
||||
// Revert kit is working by default, using the site-settings runner.
|
||||
|
||||
add_action( 'elementor/import-export/export-kit', function ( Export $export ) {
|
||||
$export->register( new Export_Runner() );
|
||||
} );
|
||||
|
||||
add_action( 'elementor/import-export/import-kit', function ( Import $import ) {
|
||||
$import->register( new Import_Runner() );
|
||||
} );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
namespace Elementor\Modules\KitElementsDefaults\ImportExport\Runners;
|
||||
|
||||
use Elementor\Modules\KitElementsDefaults\ImportExport\Import_Export;
|
||||
use Elementor\Plugin;
|
||||
use Elementor\Core\Utils\Collection;
|
||||
use Elementor\Modules\KitElementsDefaults\Module;
|
||||
use Elementor\Modules\KitElementsDefaults\Utils\Settings_Sanitizer;
|
||||
use Elementor\App\Modules\ImportExport\Runners\Export\Export_Runner_Base;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Export extends Export_Runner_Base {
|
||||
public static function get_name() : string {
|
||||
return 'elements-default-values';
|
||||
}
|
||||
|
||||
public function should_export( array $data ) {
|
||||
// Together with site-settings.
|
||||
return (
|
||||
isset( $data['include'] ) &&
|
||||
in_array( 'settings', $data['include'], true )
|
||||
);
|
||||
}
|
||||
|
||||
public function export( array $data ) {
|
||||
$kit = Plugin::$instance->kits_manager->get_active_kit();
|
||||
|
||||
if ( ! $kit ) {
|
||||
return [
|
||||
'manifest' => [],
|
||||
'files' => [],
|
||||
];
|
||||
}
|
||||
|
||||
$default_values = $kit->get_json_meta( Module::META_KEY );
|
||||
|
||||
if ( ! $default_values ) {
|
||||
return [
|
||||
'manifest' => [],
|
||||
'files' => [],
|
||||
];
|
||||
}
|
||||
|
||||
$sanitizer = new Settings_Sanitizer(
|
||||
Plugin::$instance->elements_manager,
|
||||
array_keys( Plugin::$instance->widgets_manager->get_widget_types() )
|
||||
);
|
||||
|
||||
$default_values = ( new Collection( $default_values ) )
|
||||
->map( function ( $settings, $type ) use ( $sanitizer, $kit ) {
|
||||
return $sanitizer
|
||||
->for( $type )
|
||||
->using( $settings )
|
||||
->remove_invalid_settings()
|
||||
->kses_deep()
|
||||
->prepare_for_export( $kit )
|
||||
->get();
|
||||
} )
|
||||
->all();
|
||||
|
||||
return [
|
||||
'files' => [
|
||||
'path' => Import_Export::FILE_NAME,
|
||||
'data' => $default_values,
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
namespace Elementor\Modules\KitElementsDefaults\ImportExport\Runners;
|
||||
|
||||
use Elementor\Modules\KitElementsDefaults\ImportExport\Import_Export;
|
||||
use Elementor\Plugin;
|
||||
use Elementor\Core\Utils\Collection;
|
||||
use Elementor\Modules\KitElementsDefaults\Module;
|
||||
use Elementor\App\Modules\ImportExport\Utils as ImportExportUtils;
|
||||
use Elementor\Modules\KitElementsDefaults\Utils\Settings_Sanitizer;
|
||||
use Elementor\App\Modules\ImportExport\Runners\Import\Import_Runner_Base;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Import extends Import_Runner_Base {
|
||||
public static function get_name() : string {
|
||||
return 'elements-default-values';
|
||||
}
|
||||
|
||||
public function should_import( array $data ) {
|
||||
// Together with site-settings.
|
||||
return (
|
||||
isset( $data['include'] ) &&
|
||||
in_array( 'settings', $data['include'], true ) &&
|
||||
! empty( $data['site_settings']['settings'] ) &&
|
||||
! empty( $data['extracted_directory_path'] )
|
||||
);
|
||||
}
|
||||
|
||||
public function import( array $data, array $imported_data ) {
|
||||
$kit = Plugin::$instance->kits_manager->get_active_kit();
|
||||
$file_name = Import_Export::FILE_NAME;
|
||||
$default_values = ImportExportUtils::read_json_file( "{$data['extracted_directory_path']}/{$file_name}.json" );
|
||||
|
||||
if ( ! $kit || ! $default_values ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$element_types = array_keys( Plugin::$instance->elements_manager->get_element_types() );
|
||||
$widget_types = array_keys( Plugin::$instance->widgets_manager->get_widget_types() );
|
||||
|
||||
$types = array_merge( $element_types, $widget_types );
|
||||
|
||||
$sanitizer = new Settings_Sanitizer(
|
||||
Plugin::$instance->elements_manager,
|
||||
$widget_types
|
||||
);
|
||||
|
||||
$default_values = ( new Collection( $default_values ) )
|
||||
->filter( function ( $settings, $type ) use ( $types ) {
|
||||
return in_array( $type, $types, true );
|
||||
} )
|
||||
->map( function ( $settings, $type ) use ( $sanitizer, $kit ) {
|
||||
return $sanitizer
|
||||
->for( $type )
|
||||
->using( $settings )
|
||||
->remove_invalid_settings()
|
||||
->kses_deep()
|
||||
->prepare_for_import( $kit )
|
||||
->get();
|
||||
} )
|
||||
->all();
|
||||
|
||||
$kit->update_json_meta( Module::META_KEY, $default_values );
|
||||
|
||||
return $default_values;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
namespace Elementor\Modules\KitElementsDefaults;
|
||||
|
||||
use Elementor\Core\Experiments\Manager as Experiments_Manager;
|
||||
use Elementor\Core\Base\Module as BaseModule;
|
||||
use Elementor\Modules\KitElementsDefaults\Data\Controller;
|
||||
use Elementor\Plugin;
|
||||
use Elementor\Modules\KitElementsDefaults\ImportExport\Import_Export;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
class Module extends BaseModule {
|
||||
|
||||
const META_KEY = '_elementor_elements_default_values';
|
||||
|
||||
public function get_name() {
|
||||
return 'kit-elements-defaults';
|
||||
}
|
||||
|
||||
private function enqueue_scripts() {
|
||||
wp_enqueue_script(
|
||||
'elementor-kit-elements-defaults-editor',
|
||||
$this->get_js_assets_url( 'kit-elements-defaults-editor' ),
|
||||
[
|
||||
'elementor-common',
|
||||
'elementor-editor-modules',
|
||||
'elementor-editor-document',
|
||||
'wp-i18n',
|
||||
],
|
||||
ELEMENTOR_VERSION,
|
||||
true
|
||||
);
|
||||
|
||||
wp_set_script_translations( 'elementor-kit-elements-defaults-editor', 'elementor' );
|
||||
}
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
|
||||
add_action( 'elementor/editor/before_enqueue_scripts', function () {
|
||||
$this->enqueue_scripts();
|
||||
} );
|
||||
|
||||
Plugin::$instance->data_manager_v2->register_controller( new Controller() );
|
||||
|
||||
( new Usage() )->register();
|
||||
|
||||
if ( is_admin() ) {
|
||||
( new Import_Export() )->register();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
namespace Elementor\Modules\KitElementsDefaults;
|
||||
|
||||
use Elementor\Plugin;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
class Usage {
|
||||
|
||||
public function register() {
|
||||
add_filter( 'elementor/tracker/send_tracking_data_params', function ( array $params ) {
|
||||
$params['usages']['kit']['defaults'] = $this->get_usage_data();
|
||||
|
||||
return $params;
|
||||
} );
|
||||
}
|
||||
|
||||
private function get_usage_data() {
|
||||
$elements_defaults = $this->get_elements_defaults() ?? [];
|
||||
|
||||
return [
|
||||
'count' => count( $elements_defaults ),
|
||||
'elements' => array_keys( $elements_defaults ),
|
||||
];
|
||||
}
|
||||
|
||||
private function get_elements_defaults() {
|
||||
$kit = Plugin::$instance->kits_manager->get_active_kit();
|
||||
|
||||
return $kit->get_json_meta( Module::META_KEY );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,280 @@
|
||||
<?php
|
||||
namespace Elementor\Modules\KitElementsDefaults\Utils;
|
||||
|
||||
use Elementor\Core\Breakpoints\Manager as Breakpoints_Manager;
|
||||
use Elementor\Element_Base;
|
||||
use Elementor\Elements_Manager;
|
||||
use Elementor\Core\Base\Document;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Settings_Sanitizer {
|
||||
|
||||
const SPECIAL_SETTINGS = [
|
||||
'__dynamic__',
|
||||
'__globals__',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var Elements_Manager
|
||||
*/
|
||||
private $elements_manager;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $widget_types;
|
||||
|
||||
/**
|
||||
* @var Element_Base | null
|
||||
*/
|
||||
private $pending_element = null;
|
||||
|
||||
/**
|
||||
* @var array | null
|
||||
*/
|
||||
private $pending_settings = null;
|
||||
|
||||
/**
|
||||
* @param Elements_Manager $elements_manager
|
||||
* @param array $widget_types
|
||||
*/
|
||||
public function __construct( Elements_Manager $elements_manager, array $widget_types = [] ) {
|
||||
$this->elements_manager = $elements_manager;
|
||||
$this->widget_types = $widget_types;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $type
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function for( $type ) {
|
||||
$this->pending_element = $this->create_element( $type );
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $settings
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function using( $settings ) {
|
||||
$this->pending_settings = $settings;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function reset() {
|
||||
$this->pending_element = null;
|
||||
$this->pending_settings = null;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function is_prepared() {
|
||||
return $this->pending_element && is_array( $this->pending_settings );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function remove_invalid_settings() {
|
||||
if ( ! $this->is_prepared() ) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$valid_settings_keys = $this->get_valid_settings_keys(
|
||||
$this->pending_element->get_controls()
|
||||
);
|
||||
|
||||
$this->pending_settings = $this->filter_invalid_settings(
|
||||
$this->pending_settings,
|
||||
array_merge( $valid_settings_keys, self::SPECIAL_SETTINGS )
|
||||
);
|
||||
|
||||
foreach ( self::SPECIAL_SETTINGS as $special_setting ) {
|
||||
if ( ! isset( $this->pending_settings[ $special_setting ] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->pending_settings[ $special_setting ] = $this->filter_invalid_settings(
|
||||
$this->pending_settings[ $special_setting ],
|
||||
$valid_settings_keys
|
||||
);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function kses_deep() {
|
||||
if ( ! $this->is_prepared() ) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->pending_settings = map_deep( $this->pending_settings, function( $value ) {
|
||||
if ( ! is_string( $value ) ) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
return wp_kses_post( $value );
|
||||
} );
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Document $document
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function prepare_for_export( Document $document ) {
|
||||
return $this->run_import_export_sanitize_process( $document, 'on_export' );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Document $document
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function prepare_for_import( Document $document ) {
|
||||
return $this->run_import_export_sanitize_process( $document, 'on_import' );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function get() {
|
||||
if ( ! $this->is_prepared() ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$settings = $this->pending_settings;
|
||||
|
||||
$this->reset();
|
||||
|
||||
return $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $type
|
||||
* @param array $settings
|
||||
*
|
||||
* @return Element_Base|null
|
||||
*/
|
||||
private function create_element( $type ) {
|
||||
$is_widget = in_array( $type, $this->widget_types, true );
|
||||
$is_inner_section = 'inner-section' === $type;
|
||||
|
||||
if ( $is_inner_section ) {
|
||||
return $this->elements_manager->create_element_instance( [
|
||||
'elType' => 'section',
|
||||
'isInner' => true,
|
||||
'id' => '0',
|
||||
] );
|
||||
}
|
||||
|
||||
if ( $is_widget ) {
|
||||
return $this->elements_manager->create_element_instance( [
|
||||
'elType' => 'widget',
|
||||
'widgetType' => $type,
|
||||
'id' => '0',
|
||||
] );
|
||||
}
|
||||
|
||||
return $this->elements_manager->create_element_instance( [
|
||||
'elType' => $type,
|
||||
'id' => '0',
|
||||
] );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Document $document
|
||||
* @param $process_type
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
private function run_import_export_sanitize_process( Document $document, $process_type ) {
|
||||
if ( ! $this->is_prepared() ) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$result = $document->process_element_import_export(
|
||||
$this->pending_element,
|
||||
$process_type,
|
||||
[ 'settings' => $this->pending_settings ]
|
||||
);
|
||||
|
||||
if ( empty( $result['settings'] ) ) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->pending_settings = $result['settings'];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the available settings of a specific element, including responsive settings.
|
||||
*
|
||||
* @param array $controls
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function get_valid_settings_keys( $controls ) {
|
||||
if ( ! $controls ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$control_keys = array_keys( $controls );
|
||||
|
||||
$optional_responsive_keys = [
|
||||
Breakpoints_Manager::BREAKPOINT_KEY_MOBILE,
|
||||
Breakpoints_Manager::BREAKPOINT_KEY_MOBILE_EXTRA,
|
||||
Breakpoints_Manager::BREAKPOINT_KEY_TABLET,
|
||||
Breakpoints_Manager::BREAKPOINT_KEY_TABLET_EXTRA,
|
||||
Breakpoints_Manager::BREAKPOINT_KEY_LAPTOP,
|
||||
Breakpoints_Manager::BREAKPOINT_KEY_WIDESCREEN,
|
||||
];
|
||||
|
||||
$settings = [];
|
||||
|
||||
foreach ( $control_keys as $control_key ) {
|
||||
// Add the responsive settings.
|
||||
foreach ( $optional_responsive_keys as $responsive_key ) {
|
||||
$settings[] = "{$control_key}_{$responsive_key}";
|
||||
}
|
||||
// Add the setting itself (not responsive).
|
||||
$settings[] = $control_key;
|
||||
}
|
||||
|
||||
return $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove invalid settings.
|
||||
*
|
||||
* @param $settings
|
||||
* @param $valid_settings_keys
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function filter_invalid_settings( $settings, $valid_settings_keys ) {
|
||||
return array_filter(
|
||||
$settings,
|
||||
function ( $setting_key ) use ( $valid_settings_keys ) {
|
||||
return in_array( $setting_key, $valid_settings_keys, true );
|
||||
},
|
||||
ARRAY_FILTER_USE_KEY
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user