' . esc_html__( 'برای ورود و ثبتنام با کد پیامکی، شماره موبایل شما همراه با حساب کاربریتان نگهداری میشود. کد یکبار مصرف بهصورت رمزشده و فقط تا چند دقیقه ذخیره میشود و خود کد هرگز ثبت نمیشود.', 'rahkar-toolkit' ) . '
'
. '' . esc_html__( 'برای جلوگیری از سوءاستفاده و پیگیری مشکلات ارسال، هر درخواست کد، بررسی کد و ورود با شماره موبایل و نشانی IP ثبت میشود و این گزارش پس از مدتی که مدیر سایت تعیین کرده (بهطور پیشفرض ۳۰ روز) پاک میشود.', 'rahkar-toolkit' ) . '
'
. '' . esc_html__( 'برای فرستادن پیامک، شماره موبایل و متن پیام (از جمله کد ورود) به سرویس پیامکی IPPanel ارسال میشود.', 'rahkar-toolkit' ) . '
';
wp_add_privacy_policy_content( __( 'راهکار وردپرس', 'rahkar-toolkit' ), $content );
}
/**
* Register the exporter.
*
* @param mixed $exporters Registered exporters.
*
* @return mixed
*
* @internal Hooked to `wp_privacy_personal_data_exporters`.
*/
public static function registerExporter( $exporters ) {
if ( is_array( $exporters ) ) {
$exporters[ self::ID ] = [
'exporter_friendly_name' => __( 'ورود پیامکی', 'rahkar-toolkit' ),
'callback' => [ self::class, 'export' ],
];
}
return $exporters;
}
/**
* Register the eraser.
*
* @param mixed $erasers Registered erasers.
*
* @return mixed
*
* @internal Hooked to `wp_privacy_personal_data_erasers`.
*/
public static function registerEraser( $erasers ) {
if ( is_array( $erasers ) ) {
$erasers[ self::ID ] = [
'eraser_friendly_name' => __( 'ورود پیامکی', 'rahkar-toolkit' ),
'callback' => [ self::class, 'erase' ],
];
}
return $erasers;
}
/**
* Export one page of a person's data.
*
* @param string $email Their email address.
* @param int $page 1-based page.
*
* @return array{data: list>, done: bool}
*/
public static function export( string $email, int $page = 1 ): array {
$user = get_user_by( 'email', $email );
$phones = self::phonesOf( $user instanceof \WP_User ? $user->ID : 0 );
$data = [];
if ( 1 === $page && [] !== $phones ) {
$data[] = [
'group_id' => 'rahkar-wp-phone',
'group_label' => __( 'شماره موبایل ورود', 'rahkar-toolkit' ),
'item_id' => 'rahkar-wp-phone',
'data' => [
[
'name' => __( 'شماره موبایل', 'rahkar-toolkit' ),
'value' => implode( '، ', $phones ),
],
],
];
}
$rows = self::logRows( $user instanceof \WP_User ? $user->ID : 0, $phones, $page );
foreach ( $rows as $row ) {
$data[] = [
'group_id' => 'rahkar-wp-log',
'group_label' => __( 'گزارش ورود پیامکی', 'rahkar-toolkit' ),
'item_id' => 'rahkar-wp-log-' . $row['id'],
'data' => [
[
'name' => __( 'زمان', 'rahkar-toolkit' ),
'value' => (string) $row['created_at'] . ' UTC',
],
[
'name' => __( 'رویداد', 'rahkar-toolkit' ),
'value' => (string) $row['event'] . ' / ' . (string) $row['result'],
],
[
'name' => __( 'شماره موبایل', 'rahkar-toolkit' ),
'value' => (string) $row['phone'],
],
[
'name' => __( 'نشانی IP', 'rahkar-toolkit' ),
'value' => (string) $row['ip'],
],
],
];
}
return [
'data' => $data,
'done' => count( $rows ) < self::PAGE,
];
}
/**
* Erase one page of a person's data.
*
* @param string $email Their email address.
* @param int $page 1-based page; each call deletes a batch, so it restarts at the top.
*
* @return array{items_removed: bool, items_retained: bool, messages: list, done: bool}
*/
public static function erase( string $email, int $page = 1 ): array {
global $wpdb;
unset( $page );
$user = get_user_by( 'email', $email );
$userId = $user instanceof \WP_User ? $user->ID : 0;
$phones = self::phonesOf( $userId );
$ids = array_map( static fn( array $row ): int => (int) $row['id'], self::logRows( $userId, $phones, 1 ) );
$removed = 0;
if ( [] !== $ids ) {
// phpcs:disable WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber -- One %d per id, built from their count.
$removed = (int) $wpdb->query(
$wpdb->prepare(
'DELETE FROM %i WHERE id IN (' . implode( ',', array_fill( 0, count( $ids ), '%d' ) ) . ')',
array_merge( [ Schema::table( 'otp_log' ) ], $ids )
)
);
// phpcs:enable WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber, PluginCheck.Security.DirectDB.UnescapedDBParameter
}
foreach ( $phones as $phone ) {
// phpcs:ignore WordPress.DB.DirectDatabaseQuery
$removed += (int) $wpdb->delete( Schema::table( 'otp' ), [ 'identifier' => $phone ], [ '%s' ] );
}
$done = count( $ids ) < self::PAGE;
return [
'items_removed' => $removed > 0,
'items_retained' => $done && [] !== $phones,
'messages' => $done && [] !== $phones
? [ __( 'شماره موبایل حساب نگه داشته شد، چون راه ورود کاربر به همین حساب است. با حذف حساب کاربری، این شماره هم حذف میشود.', 'rahkar-toolkit' ) ]
: [],
'done' => $done,
];
}
/**
* The mobile numbers linked to an account, canonical.
*
* @param int $userId User id; 0 for none.
*
* @return list
*/
private static function phonesOf( int $userId ): array {
global $wpdb;
if ( $userId <= 0 ) {
return [];
}
// phpcs:ignore WordPress.DB.DirectDatabaseQuery
$phones = $wpdb->get_col(
$wpdb->prepare(
'SELECT value FROM %i WHERE user_id = %d AND type = %s',
Schema::table( 'identity' ),
$userId,
'phone'
)
);
$meta = (string) get_user_meta( $userId, 'rahkar_phone', true );
return array_values( array_unique( array_filter( array_merge( array_map( 'strval', (array) $phones ), [ $meta ] ) ) ) );
}
/**
* One page of sign-in log rows about a person: by account or by number.
*
* @param int $userId User id; 0 for none.
* @param list $phones Their numbers.
* @param int $page 1-based page.
*
* @return list>
*/
private static function logRows( int $userId, array $phones, int $page ): array {
global $wpdb;
if ( $userId <= 0 && [] === $phones ) {
return [];
}
$where = [ 'user_id = %d' ];
$args = [ max( 0, $userId ) ];
if ( [] !== $phones ) {
$where[] = 'phone IN (' . implode( ',', array_fill( 0, count( $phones ), '%s' ) ) . ')';
$args = array_merge( $args, $phones );
}
// phpcs:disable WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Fixed fragments; one placeholder per value.
$rows = $wpdb->get_results(
$wpdb->prepare(
'SELECT id, event, result, phone, ip, created_at FROM %i WHERE (' . implode( ' OR ', $where ) . ') ORDER BY id LIMIT %d OFFSET %d',
array_merge( [ Schema::table( 'otp_log' ) ], $args, [ self::PAGE, ( max( 1, $page ) - 1 ) * self::PAGE ] )
),
ARRAY_A
);
// phpcs:enable WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber, PluginCheck.Security.DirectDB.UnescapedDBParameter
return is_array( $rows ) ? array_values( $rows ) : [];
}
}