updated plugin Easy Digital Downloads
version 3.1.1.2
This commit is contained in:
@ -11,6 +11,9 @@
|
||||
// Exit if accessed directly
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
use EDD\Licensing\API;
|
||||
use EDD\Licensing\License;
|
||||
|
||||
if ( ! class_exists( 'EDD_License' ) ) :
|
||||
|
||||
/**
|
||||
@ -24,7 +27,31 @@ class EDD_License {
|
||||
private $item_shortname;
|
||||
private $version;
|
||||
private $author;
|
||||
private $api_url = 'https://easydigitaldownloads.com/edd-sl-api/';
|
||||
private $api_url;
|
||||
private $api_handler;
|
||||
|
||||
/**
|
||||
* The pass manager.
|
||||
*
|
||||
* @var \EDD\Admin\Pass_Manager
|
||||
*/
|
||||
private $pass_manager;
|
||||
|
||||
/**
|
||||
* The EDD license object.
|
||||
* This contains standard license data (from the API response) and the license key.
|
||||
*
|
||||
* @var \EDD\Licensing\License
|
||||
*/
|
||||
private $edd_license;
|
||||
|
||||
/**
|
||||
* Whether the license being checked is a pro license.
|
||||
*
|
||||
* @since 3.1.1
|
||||
* @var bool
|
||||
*/
|
||||
private $is_pro_license = false;
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
@ -47,23 +74,18 @@ class EDD_License {
|
||||
|
||||
$this->item_shortname = 'edd_' . preg_replace( '/[^a-zA-Z0-9_\s]/', '', str_replace( ' ', '_', strtolower( $this->item_name ) ) );
|
||||
$this->version = $_version;
|
||||
$this->license = trim( edd_get_option( $this->item_shortname . '_license_key', '' ) );
|
||||
$this->author = $_author;
|
||||
$this->api_url = is_null( $_api_url ) ? $this->api_url : $_api_url;
|
||||
|
||||
/**
|
||||
* Allows for backwards compatibility with old license options,
|
||||
* i.e. if the plugins had license key fields previously, the license
|
||||
* handler will automatically pick these up and use those in lieu of the
|
||||
* user having to reactive their license.
|
||||
*/
|
||||
if ( ! empty( $_optname ) ) {
|
||||
$opt = edd_get_option( $_optname, false );
|
||||
|
||||
if ( isset( $opt ) && empty( $this->license ) ) {
|
||||
$this->license = trim( $opt );
|
||||
$this->edd_license = new License( $this->item_name, $_optname );
|
||||
if ( empty( $this->edd_license->key ) ) {
|
||||
$this->edd_license = new License( 'pro' );
|
||||
if ( ! empty( $this->edd_license->key ) ) {
|
||||
$this->is_pro_license = true;
|
||||
}
|
||||
}
|
||||
$this->license = $this->edd_license->key;
|
||||
$this->author = $_author;
|
||||
$this->api_handler = new API();
|
||||
$this->api_url = is_null( $_api_url ) ? $this->api_handler->get_url() : $_api_url;
|
||||
$this->pass_manager = new \EDD\Admin\Pass_Manager();
|
||||
|
||||
// Setup hooks
|
||||
$this->includes();
|
||||
@ -85,7 +107,6 @@ class EDD_License {
|
||||
}
|
||||
$edd_licensed_products[] = $this->item_shortname;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -114,12 +135,6 @@ class EDD_License {
|
||||
// Display help text at the top of the Licenses tab
|
||||
add_action( 'edd_settings_tab_top', array( $this, 'license_help_text' ) );
|
||||
|
||||
// Activate license key on settings save
|
||||
add_action( 'admin_init', array( $this, 'activate_license' ) );
|
||||
|
||||
// Deactivate license key
|
||||
add_action( 'admin_init', array( $this, 'deactivate_license' ) );
|
||||
|
||||
// Check that license is valid once per week
|
||||
if ( edd_doing_cron() ) {
|
||||
add_action( 'edd_weekly_scheduled_events', array( $this, 'weekly_license_check' ) );
|
||||
@ -160,23 +175,27 @@ class EDD_License {
|
||||
}
|
||||
|
||||
$license = $this->license;
|
||||
// Fall back to the highest license key if one is not saved for this extension.
|
||||
// Fall back to the highest license key if one is not saved for this extension or there isn't a pro license.
|
||||
if ( empty( $license ) ) {
|
||||
$pass_manager = new \EDD\Admin\Pass_Manager();
|
||||
if ( $pass_manager->highest_license_key ) {
|
||||
$license = $pass_manager->highest_license_key;
|
||||
if ( $this->pass_manager->highest_license_key ) {
|
||||
$license = $this->pass_manager->highest_license_key;
|
||||
}
|
||||
}
|
||||
|
||||
// Don't check for updates if there isn't a license key.
|
||||
if ( empty( $license ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$args = array(
|
||||
'version' => $this->version,
|
||||
'license' => $license,
|
||||
'author' => $this->author,
|
||||
'beta' => function_exists( 'edd_extension_has_beta_support' ) && edd_extension_has_beta_support( $this->item_shortname ),
|
||||
'version' => $this->version,
|
||||
'license' => $license,
|
||||
'author' => $this->author,
|
||||
'beta' => function_exists( 'edd_extension_has_beta_support' ) && edd_extension_has_beta_support( $this->item_shortname ),
|
||||
);
|
||||
|
||||
if ( ! empty( $this->item_id ) ) {
|
||||
$args['item_id'] = $this->item_id;
|
||||
$args['item_id'] = $this->item_id;
|
||||
} else {
|
||||
$args['item_name'] = $this->item_name;
|
||||
}
|
||||
@ -199,11 +218,14 @@ class EDD_License {
|
||||
return array_merge( $settings, array(
|
||||
array(
|
||||
'id' => $this->item_shortname . '_license_key',
|
||||
'name' => sprintf( __( '%1$s', 'easy-digital-downloads' ), $this->item_name ),
|
||||
'name' => $this->item_name,
|
||||
'desc' => '',
|
||||
'type' => 'license_key',
|
||||
'options' => array( 'is_valid_license_option' => $this->item_shortname . '_license_active' ),
|
||||
'size' => 'regular'
|
||||
'options' => array(
|
||||
'is_valid_license_option' => $this->item_shortname . '_license_active',
|
||||
'item_id' => $this->item_id,
|
||||
),
|
||||
'size' => 'regular',
|
||||
)
|
||||
) );
|
||||
}
|
||||
@ -226,208 +248,42 @@ class EDD_License {
|
||||
return;
|
||||
}
|
||||
|
||||
echo '<p>' . sprintf(
|
||||
__( 'Enter your extension license keys here to receive updates for purchased extensions. If your license key has expired, please <a href="%s" target="_blank">renew your license</a>.', 'easy-digital-downloads' ),
|
||||
'https://docs.easydigitaldownloads.com/article/1000-license-renewal'
|
||||
) . '</p>';
|
||||
?>
|
||||
<div class="edd-licenses__description">
|
||||
<p>
|
||||
<?php
|
||||
printf(
|
||||
__( 'Enter your extension license keys here to receive updates for purchased extensions. If your license key has expired, please %srenew your license%s.', 'easy-digital-downloads' ),
|
||||
'<a href="https://easydigitaldownloads.com/docs/license-renewal/" target="_blank">',
|
||||
'</a>'
|
||||
);
|
||||
?>
|
||||
</p>
|
||||
<?php if ( ! $this->pass_manager->highest_license_key ) : ?>
|
||||
<p>
|
||||
<?php
|
||||
$url = edd_get_admin_url(
|
||||
array(
|
||||
'page' => 'edd-settings',
|
||||
'tab' => 'general',
|
||||
)
|
||||
);
|
||||
printf(
|
||||
__( 'Have a pass? You\'re ready to set up EDD (Pro). %1$sActivate Your Pass%2$s' ),
|
||||
'<a href="' . esc_url( $url ) . '" class="button button-primary">',
|
||||
'</a>'
|
||||
);
|
||||
?>
|
||||
</p>
|
||||
<?php
|
||||
endif;
|
||||
?>
|
||||
</div>
|
||||
<?php
|
||||
|
||||
$has_ran = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the supplied license key is for a pass, updates the `edd_pass_licenses` option with
|
||||
* the pass ID and the date it was checked.
|
||||
*
|
||||
* Note: It's intentional that the `edd_pass_licenses` option is always updated, even if
|
||||
* the provided license data is not for a pass. This is so we have a clearer idea
|
||||
* of when the checks started coming through. If the option doesn't exist in the DB
|
||||
* at all, then we haven't checked any licenses.
|
||||
*
|
||||
* @since 2.10.6
|
||||
*
|
||||
* @param string $license
|
||||
* @param object $api_data
|
||||
*/
|
||||
private function maybe_set_pass_flag( $license, $api_data ) {
|
||||
$passes = get_option( 'edd_pass_licenses' );
|
||||
$passes = ! empty( $passes ) ? json_decode( $passes, true ) : array();
|
||||
|
||||
if ( ! empty( $api_data->pass_id ) && ! empty( $api_data->license ) && 'valid' === $api_data->license ) {
|
||||
$passes[ $license ] = array(
|
||||
'pass_id' => intval( $api_data->pass_id ),
|
||||
'time_checked' => time()
|
||||
);
|
||||
} else if ( array_key_exists( $license, $passes ) ) {
|
||||
unset( $passes[ $license ] );
|
||||
}
|
||||
|
||||
update_option( 'edd_pass_licenses', json_encode( $passes ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the pass flag for the supplied license. This happens when a license
|
||||
* is deactivated.
|
||||
*
|
||||
* @since 2.10.6
|
||||
*
|
||||
* @param string $license
|
||||
*/
|
||||
private function maybe_remove_pass_flag( $license ) {
|
||||
$passes = get_option( 'edd_pass_licenses' );
|
||||
$passes = ! empty( $passes ) ? json_decode( $passes, true ) : array();
|
||||
|
||||
if ( array_key_exists( $license, $passes ) ) {
|
||||
unset( $passes[ $license ] );
|
||||
}
|
||||
|
||||
update_option( 'edd_pass_licenses', json_encode( $passes ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Activate the license key
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function activate_license() {
|
||||
|
||||
if ( ! isset( $_POST['edd_settings'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! isset( $_REQUEST[ $this->item_shortname . '_license_key-nonce'] ) || ! wp_verify_nonce( $_REQUEST[ $this->item_shortname . '_license_key-nonce'], $this->item_shortname . '_license_key-nonce' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! current_user_can( 'manage_shop_settings' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( empty( $_POST['edd_settings'][ $this->item_shortname . '_license_key'] ) ) {
|
||||
delete_option( $this->item_shortname . '_license_active' );
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ( $_POST as $key => $value ) {
|
||||
if ( false !== strpos( $key, 'license_key_deactivate' ) ) {
|
||||
// Don't activate a key when deactivating a different key
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$details = get_option( $this->item_shortname . '_license_active' );
|
||||
|
||||
if ( is_object( $details ) && ! empty( $details->license ) && 'valid' === $details->license ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$license = sanitize_text_field( $_POST['edd_settings'][ $this->item_shortname . '_license_key'] );
|
||||
|
||||
if ( empty( $license ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Data to send to the API
|
||||
$api_params = array(
|
||||
'edd_action' => 'activate_license',
|
||||
'license' => $license,
|
||||
'item_name' => urlencode( $this->item_name ),
|
||||
'url' => home_url()
|
||||
);
|
||||
|
||||
if ( ! empty( $this->item_id ) ) {
|
||||
$api_params['item_id'] = $this->item_id;
|
||||
}
|
||||
|
||||
// Call the API
|
||||
$response = wp_remote_post(
|
||||
$this->api_url,
|
||||
array(
|
||||
'timeout' => 15,
|
||||
'sslverify' => false,
|
||||
'body' => $api_params
|
||||
)
|
||||
);
|
||||
|
||||
// Make sure there are no errors
|
||||
if ( is_wp_error( $response ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Tell WordPress to look for updates
|
||||
set_site_transient( 'update_plugins', null );
|
||||
|
||||
// Decode license data
|
||||
$license_data = json_decode( wp_remote_retrieve_body( $response ) );
|
||||
|
||||
$this->maybe_set_pass_flag( $license, $license_data );
|
||||
|
||||
// Clear the option for licensed extensions to force regeneration.
|
||||
if ( ! empty( $api_data->license ) && 'valid' === $api_data->license ) {
|
||||
delete_option( 'edd_licensed_extensions' );
|
||||
}
|
||||
|
||||
update_option( $this->item_shortname . '_license_active', $license_data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Deactivate the license key
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function deactivate_license() {
|
||||
|
||||
if ( ! isset( $_POST['edd_settings'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! isset( $_POST['edd_settings'][ $this->item_shortname . '_license_key'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! wp_verify_nonce( $_REQUEST[ $this->item_shortname . '_license_key-nonce'], $this->item_shortname . '_license_key-nonce' ) ) {
|
||||
wp_die( __( 'Nonce verification failed', 'easy-digital-downloads' ), __( 'Error', 'easy-digital-downloads' ), array( 'response' => 403 ) );
|
||||
}
|
||||
|
||||
if ( ! current_user_can( 'manage_shop_settings' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Run on deactivate button press
|
||||
if ( isset( $_POST[ $this->item_shortname . '_license_key_deactivate'] ) ) {
|
||||
|
||||
// Data to send to the API
|
||||
$api_params = array(
|
||||
'edd_action' => 'deactivate_license',
|
||||
'license' => $this->license,
|
||||
'item_name' => urlencode( $this->item_name ),
|
||||
'url' => home_url()
|
||||
);
|
||||
|
||||
if ( ! empty( $this->item_id ) ) {
|
||||
$api_params['item_id'] = $this->item_id;
|
||||
}
|
||||
|
||||
// Call the API
|
||||
$response = wp_remote_post(
|
||||
$this->api_url,
|
||||
array(
|
||||
'timeout' => 15,
|
||||
'sslverify' => false,
|
||||
'body' => $api_params
|
||||
)
|
||||
);
|
||||
|
||||
// Make sure there are no errors
|
||||
if ( is_wp_error( $response ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->maybe_remove_pass_flag( $this->license );
|
||||
|
||||
delete_option( $this->item_shortname . '_license_active' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if license key is valid once per week
|
||||
*
|
||||
@ -436,8 +292,14 @@ class EDD_License {
|
||||
*/
|
||||
public function weekly_license_check() {
|
||||
|
||||
// If a pro license is active, that license check is handled separately.
|
||||
if ( $this->is_pro_license ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't fire when saving settings.
|
||||
if ( ! empty( $_POST['edd_settings'] ) ) {
|
||||
return; // Don't fire when saving settings
|
||||
return;
|
||||
}
|
||||
|
||||
if ( empty( $this->license ) ) {
|
||||
@ -446,36 +308,22 @@ class EDD_License {
|
||||
|
||||
// data to send in our API request
|
||||
$api_params = array(
|
||||
'edd_action'=> 'check_license',
|
||||
'license' => $this->license,
|
||||
'item_name' => urlencode( $this->item_name ),
|
||||
'url' => home_url()
|
||||
'edd_action' => 'check_license',
|
||||
'license' => $this->license,
|
||||
'item_name' => urlencode( $this->item_name ),
|
||||
);
|
||||
|
||||
if ( ! empty( $this->item_id ) ) {
|
||||
$api_params['item_id'] = $this->item_id;
|
||||
}
|
||||
|
||||
// Call the API
|
||||
$response = wp_remote_post(
|
||||
$this->api_url,
|
||||
array(
|
||||
'timeout' => 15,
|
||||
'sslverify' => false,
|
||||
'body' => $api_params
|
||||
)
|
||||
);
|
||||
|
||||
// make sure the response came back okay
|
||||
if ( is_wp_error( $response ) ) {
|
||||
$license_data = $this->api_handler->make_request( $api_params );
|
||||
if ( ! $license_data ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$license_data = json_decode( wp_remote_retrieve_body( $response ) );
|
||||
|
||||
$this->maybe_set_pass_flag( $this->license, $license_data );
|
||||
|
||||
update_option( $this->item_shortname . '_license_active', $license_data );
|
||||
$this->pass_manager->maybe_set_pass_flag( $this->license, $license_data );
|
||||
$this->edd_license->save( $license_data );
|
||||
}
|
||||
|
||||
/**
|
||||
@ -496,9 +344,9 @@ class EDD_License {
|
||||
|
||||
$messages = array();
|
||||
|
||||
$license = get_option( $this->item_shortname . '_license_active' );
|
||||
$license = $this->edd_license;
|
||||
|
||||
if ( is_object( $license ) && ( empty( $license->license ) || 'valid' !== $license->license ) && empty( $showed_invalid_message ) ) {
|
||||
if ( ( empty( $license->license ) || 'valid' !== $license->license ) && empty( $showed_invalid_message ) ) {
|
||||
if ( empty( $_GET['tab'] ) || 'licenses' !== $_GET['tab'] ) {
|
||||
|
||||
$messages[] = sprintf(
|
||||
@ -528,9 +376,9 @@ class EDD_License {
|
||||
public function plugin_row_license_missing( $plugin_data, $version_info ) {
|
||||
static $showed_imissing_key_message = array();
|
||||
|
||||
$license = get_option( $this->item_shortname . '_license_active' );
|
||||
$license = $this->edd_license;
|
||||
|
||||
if ( ( ! is_object( $license ) || empty( $license->license ) || 'valid' !== $license->license ) && empty( $showed_imissing_key_message[ $this->item_shortname ] ) ) {
|
||||
if ( ( empty( $license->license ) || 'valid' !== $license->license ) && empty( $showed_imissing_key_message[ $this->item_shortname ] ) ) {
|
||||
echo ' <strong><a href="' . esc_url( admin_url( 'edit.php?post_type=download&page=edd-settings&tab=licenses' ) ) . '">' . __( 'Enter valid license key for automatic updates.', 'easy-digital-downloads' ) . '</a></strong>';
|
||||
$showed_imissing_key_message[ $this->item_shortname ] = true;
|
||||
}
|
||||
@ -592,6 +440,134 @@ class EDD_License {
|
||||
delete_option( 'edd_stripe_payment_gateway_license_key_active' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Activate the license key.
|
||||
*
|
||||
* @deprecated 3.1.1
|
||||
* @return void
|
||||
*/
|
||||
public function activate_license() {
|
||||
|
||||
if ( ! isset( $_POST['edd_settings'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! isset( $_REQUEST[ $this->item_shortname . '_license_key-nonce'] ) || ! wp_verify_nonce( $_REQUEST[ $this->item_shortname . '_license_key-nonce'], $this->item_shortname . '_license_key-nonce' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! current_user_can( 'manage_shop_settings' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( empty( $_POST['edd_settings'][ $this->item_shortname . '_license_key'] ) ) {
|
||||
delete_option( $this->item_shortname . '_license_active' );
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ( $_POST as $key => $value ) {
|
||||
if ( false !== strpos( $key, 'license_key_deactivate' ) ) {
|
||||
// Don't activate a key when deactivating a different key
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if ( 'valid' === $this->edd_license->license ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$license = sanitize_text_field( $_POST['edd_settings'][ $this->item_shortname . '_license_key' ] );
|
||||
|
||||
if ( empty( $license ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Data to send to the API
|
||||
$api_params = array(
|
||||
'edd_action' => 'activate_license',
|
||||
'license' => $license,
|
||||
'item_name' => urlencode( $this->item_name ),
|
||||
'url' => home_url()
|
||||
);
|
||||
|
||||
if ( ! empty( $this->item_id ) ) {
|
||||
$api_params['item_id'] = $this->item_id;
|
||||
}
|
||||
|
||||
// Call the API
|
||||
$license_data = $this->api_handler->make_request( $api_params );
|
||||
|
||||
// Make sure there are no errors
|
||||
if ( ! $license_data ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Tell WordPress to look for updates
|
||||
set_site_transient( 'update_plugins', null );
|
||||
|
||||
$this->pass_manager->maybe_set_pass_flag( $license, $license_data );
|
||||
|
||||
// Clear the option for licensed extensions to force regeneration.
|
||||
if ( ! empty( $license_data->license ) && 'valid' === $license_data->license ) {
|
||||
delete_option( 'edd_licensed_extensions' );
|
||||
}
|
||||
|
||||
$this->edd_license->save( $license_data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Deactivate the license key
|
||||
*
|
||||
* @deprecated 3.1.1
|
||||
* @return void
|
||||
*/
|
||||
public function deactivate_license() {
|
||||
|
||||
if ( ! isset( $_POST['edd_settings'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! isset( $_POST['edd_settings'][ $this->item_shortname . '_license_key'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! wp_verify_nonce( $_REQUEST[ $this->item_shortname . '_license_key-nonce'], $this->item_shortname . '_license_key-nonce' ) ) {
|
||||
wp_die( __( 'Nonce verification failed', 'easy-digital-downloads' ), __( 'Error', 'easy-digital-downloads' ), array( 'response' => 403 ) );
|
||||
}
|
||||
|
||||
if ( ! current_user_can( 'manage_shop_settings' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Run on deactivate button press
|
||||
if ( isset( $_POST[ $this->item_shortname . '_license_key_deactivate'] ) ) {
|
||||
|
||||
// Data to send to the API
|
||||
$api_params = array(
|
||||
'edd_action' => 'deactivate_license',
|
||||
'license' => $this->license,
|
||||
'item_name' => urlencode( $this->item_name ),
|
||||
'url' => home_url()
|
||||
);
|
||||
|
||||
if ( ! empty( $this->item_id ) ) {
|
||||
$api_params['item_id'] = $this->item_id;
|
||||
}
|
||||
|
||||
// Call the API
|
||||
$response = $this->api_handler->make_request( $api_params );
|
||||
|
||||
// Make sure there are no errors
|
||||
if ( ! $response ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->pass_manager->maybe_remove_pass_flag( $this->license );
|
||||
|
||||
delete_option( $this->item_shortname . '_license_active' );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
endif; // end class_exists check
|
||||
|
Reference in New Issue
Block a user