Add aksrt_download_logs workbench, zibll hidecontent alias + topic partial-hide path, VIP1/VIP2 price matrix for topics, and users list commerce snapshot column with deep links.
407 lines
21 KiB
PHP
407 lines
21 KiB
PHP
<?php
|
|
/**
|
|
* Native topic governance meta box for aksrt_forum_topic.
|
|
*
|
|
* Visibility / pay / credits contract (runtime source of truth):
|
|
* - `_aksrt_visibility` public|login|comment|role|credits|pay|moderator
|
|
* - `_aksrt_credits_cost` points price when visibility=credits
|
|
* - `_aksrt_pay_price` cash price when visibility=pay
|
|
* - `_aksrt_pay_vip_price` optional VIP cash (0 = free)
|
|
* - `_aksrt_visibility_roles` roles when visibility=role
|
|
* - `_aksrt_pay_hide_part` partial hide flag (full hide still default)
|
|
*
|
|
* Legacy `_aksrt_pay_credits` / topic `_aksrt_allow_view` are rewritten on save.
|
|
*
|
|
* @package AKSRT
|
|
*/
|
|
|
|
defined('ABSPATH') || exit;
|
|
|
|
class AKSRT_Topic_Meta_Box {
|
|
|
|
private const VISIBILITY = [
|
|
'public',
|
|
'login',
|
|
'comment',
|
|
'role',
|
|
'credits',
|
|
'pay',
|
|
'moderator',
|
|
];
|
|
|
|
private static $instance = null;
|
|
|
|
public static function get_instance(): self {
|
|
if (null === self::$instance) {
|
|
self::$instance = new self();
|
|
}
|
|
return self::$instance;
|
|
}
|
|
|
|
public function init(): void {
|
|
if (!is_admin()) {
|
|
return;
|
|
}
|
|
add_action('add_meta_boxes', [$this, 'register_meta_box']);
|
|
add_action('save_post_aksrt_forum_topic', [$this, 'save'], 20, 2);
|
|
}
|
|
|
|
public function register_meta_box(): void {
|
|
add_meta_box(
|
|
'aksrt_topic_governance',
|
|
__('帖子治理', 'aksrt'),
|
|
[$this, 'render'],
|
|
'aksrt_forum_topic',
|
|
'side',
|
|
'high'
|
|
);
|
|
}
|
|
|
|
public function render(WP_Post $post): void {
|
|
wp_nonce_field('aksrt_topic_meta_save', 'aksrt_topic_meta_nonce');
|
|
|
|
$plate_id = (int) get_post_meta($post->ID, '_aksrt_plate_id', true);
|
|
$sticky = (int) get_post_meta($post->ID, '_aksrt_sticky', true);
|
|
$locked = (int) get_post_meta($post->ID, '_aksrt_locked', true);
|
|
$featured = (int) get_post_meta($post->ID, '_aksrt_featured', true);
|
|
$review = (string) get_post_meta($post->ID, '_aksrt_review_status', true);
|
|
if ($review === '') {
|
|
$review = 'approved';
|
|
}
|
|
$bbs_type = (string) (get_post_meta($post->ID, '_aksrt_bbs_type', true) ?: 'standard');
|
|
$visibility = sanitize_key((string) (get_post_meta($post->ID, '_aksrt_visibility', true) ?: 'public'));
|
|
if (!in_array($visibility, self::VISIBILITY, true)) {
|
|
$visibility = 'public';
|
|
}
|
|
|
|
$credits_cost = (int) get_post_meta($post->ID, '_aksrt_credits_cost', true);
|
|
if ($credits_cost <= 0) {
|
|
// Legacy dual key.
|
|
$credits_cost = max(0, (int) get_post_meta($post->ID, '_aksrt_pay_credits', true));
|
|
}
|
|
$pay_price = (float) get_post_meta($post->ID, '_aksrt_pay_price', true);
|
|
$pay_vip_price = get_post_meta($post->ID, '_aksrt_pay_vip_price', true);
|
|
$vip_set = ($pay_vip_price !== '' && $pay_vip_price !== null && $pay_vip_price !== false);
|
|
$vip_price_val = $vip_set ? (float) $pay_vip_price : '';
|
|
$pay_vip1_price = get_post_meta($post->ID, '_aksrt_pay_vip1_price', true);
|
|
$pay_vip2_price = get_post_meta($post->ID, '_aksrt_pay_vip2_price', true);
|
|
$vip1_set = ($pay_vip1_price !== '' && $pay_vip1_price !== null && $pay_vip1_price !== false);
|
|
$vip2_set = ($pay_vip2_price !== '' && $pay_vip2_price !== null && $pay_vip2_price !== false);
|
|
// Migrate single VIP price into vip1 display if vip1 empty.
|
|
if (!$vip1_set && $vip_set) {
|
|
$vip1_set = true;
|
|
$pay_vip1_price = $pay_vip_price;
|
|
}
|
|
$credits_vip1 = get_post_meta($post->ID, '_aksrt_credits_vip1', true);
|
|
$credits_vip2 = get_post_meta($post->ID, '_aksrt_credits_vip2', true);
|
|
$credits_vip1_set = ($credits_vip1 !== '' && $credits_vip1 !== null && $credits_vip1 !== false);
|
|
$credits_vip2_set = ($credits_vip2 !== '' && $credits_vip2 !== null && $credits_vip2 !== false);
|
|
$pay_hide_part = (int) get_post_meta($post->ID, '_aksrt_pay_hide_part', true) === 1;
|
|
$view_roles = get_post_meta($post->ID, '_aksrt_visibility_roles', true);
|
|
if (!is_array($view_roles)) {
|
|
$view_roles = [];
|
|
}
|
|
$view_roles = array_values(array_filter(array_map('sanitize_key', $view_roles)));
|
|
$wp_roles = $this->wp_role_options();
|
|
|
|
$plates = get_posts([
|
|
'post_type' => 'aksrt_forum_plate',
|
|
'post_status' => 'publish',
|
|
'posts_per_page' => 200,
|
|
'orderby' => 'title',
|
|
'order' => 'ASC',
|
|
]);
|
|
|
|
$visibility_labels = [
|
|
'public' => __('公开', 'aksrt'),
|
|
'login' => __('登录可见', 'aksrt'),
|
|
'comment' => __('评论可见', 'aksrt'),
|
|
'role' => __('指定角色', 'aksrt'),
|
|
'credits' => __('支付积分后可见', 'aksrt'),
|
|
'pay' => __('付费后可见', 'aksrt'),
|
|
'moderator' => __('仅版主', 'aksrt'),
|
|
];
|
|
?>
|
|
<p>
|
|
<label for="aksrt_plate_id"><strong><?php esc_html_e('所属版块', 'aksrt'); ?></strong></label>
|
|
<select name="aksrt_plate_id" id="aksrt_plate_id" class="widefat">
|
|
<option value="0"><?php esc_html_e('未指定', 'aksrt'); ?></option>
|
|
<?php foreach ($plates as $plate) : ?>
|
|
<option value="<?php echo esc_attr((string) $plate->ID); ?>" <?php selected($plate_id, (int) $plate->ID); ?>>
|
|
<?php echo esc_html(get_the_title($plate)); ?>
|
|
</option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</p>
|
|
<p>
|
|
<label for="aksrt_sticky"><strong><?php esc_html_e('置顶', 'aksrt'); ?></strong></label>
|
|
<select name="aksrt_sticky" id="aksrt_sticky" class="widefat">
|
|
<option value="0" <?php selected($sticky, 0); ?>><?php esc_html_e('否', 'aksrt'); ?></option>
|
|
<option value="1" <?php selected($sticky, 1); ?>><?php esc_html_e('版块置顶', 'aksrt'); ?></option>
|
|
<option value="2" <?php selected($sticky, 2); ?>><?php esc_html_e('超级置顶', 'aksrt'); ?></option>
|
|
<option value="3" <?php selected($sticky, 3); ?>><?php esc_html_e('全局置顶', 'aksrt'); ?></option>
|
|
</select>
|
|
</p>
|
|
<p>
|
|
<label><input type="checkbox" name="aksrt_locked" value="1" <?php checked($locked, 1); ?>> <?php esc_html_e('锁定回复', 'aksrt'); ?></label><br>
|
|
<label><input type="checkbox" name="aksrt_featured" value="1" <?php checked($featured, 1); ?>> <?php esc_html_e('精华', 'aksrt'); ?></label>
|
|
</p>
|
|
<p>
|
|
<label for="aksrt_review_status"><strong><?php esc_html_e('审核状态', 'aksrt'); ?></strong></label>
|
|
<select name="aksrt_review_status" id="aksrt_review_status" class="widefat">
|
|
<option value="approved" <?php selected($review, 'approved'); ?>><?php esc_html_e('已通过', 'aksrt'); ?></option>
|
|
<option value="pending" <?php selected($review, 'pending'); ?>><?php esc_html_e('待审核', 'aksrt'); ?></option>
|
|
<option value="rejected" <?php selected($review, 'rejected'); ?>><?php esc_html_e('已拒绝', 'aksrt'); ?></option>
|
|
</select>
|
|
</p>
|
|
<p>
|
|
<label for="aksrt_bbs_type"><strong><?php esc_html_e('帖子类型', 'aksrt'); ?></strong></label>
|
|
<select name="aksrt_bbs_type" id="aksrt_bbs_type" class="widefat">
|
|
<option value="standard" <?php selected($bbs_type, 'standard'); ?>><?php esc_html_e('标准', 'aksrt'); ?></option>
|
|
<option value="question" <?php selected($bbs_type, 'question'); ?>><?php esc_html_e('提问', 'aksrt'); ?></option>
|
|
</select>
|
|
</p>
|
|
<p>
|
|
<label for="aksrt_visibility"><strong><?php esc_html_e('可见性', 'aksrt'); ?></strong></label>
|
|
<select name="aksrt_visibility" id="aksrt_visibility" class="widefat">
|
|
<?php foreach ($visibility_labels as $k => $label) : ?>
|
|
<option value="<?php echo esc_attr($k); ?>" <?php selected($visibility, $k); ?>><?php echo esc_html($label); ?></option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</p>
|
|
<p class="aksrt-topic-meta-role-fields">
|
|
<label for="aksrt_visibility_roles"><strong><?php esc_html_e('可查看角色', 'aksrt'); ?></strong></label>
|
|
<select id="aksrt_visibility_roles" name="aksrt_visibility_roles[]" class="widefat" multiple size="4">
|
|
<?php foreach ($wp_roles as $role_key => $role_label) : ?>
|
|
<option value="<?php echo esc_attr($role_key); ?>" <?php selected(in_array($role_key, $view_roles, true)); ?>>
|
|
<?php echo esc_html($role_label); ?>
|
|
</option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
<span class="description"><?php esc_html_e('仅「指定角色」时生效。', 'aksrt'); ?></span>
|
|
</p>
|
|
<p class="aksrt-topic-meta-credits-fields">
|
|
<label for="aksrt_credits_cost"><strong><?php esc_html_e('积分价格', 'aksrt'); ?></strong></label>
|
|
<input type="number" step="1" min="0" class="widefat" name="aksrt_credits_cost" id="aksrt_credits_cost" value="<?php echo esc_attr((string) $credits_cost); ?>">
|
|
<span class="description"><?php esc_html_e('可见性为「支付积分后可见」时使用。', 'aksrt'); ?></span>
|
|
</p>
|
|
<p class="aksrt-topic-meta-credits-fields">
|
|
<label for="aksrt_credits_vip1"><strong><?php esc_html_e('VIP1 积分价', 'aksrt'); ?></strong></label>
|
|
<input type="number" step="1" min="0" class="widefat" name="aksrt_credits_vip1" id="aksrt_credits_vip1" value="<?php echo esc_attr($credits_vip1_set ? (string) (int) $credits_vip1 : ''); ?>" placeholder="<?php esc_attr_e('留空=不设', 'aksrt'); ?>">
|
|
</p>
|
|
<p class="aksrt-topic-meta-credits-fields">
|
|
<label for="aksrt_credits_vip2"><strong><?php esc_html_e('VIP2 积分价', 'aksrt'); ?></strong></label>
|
|
<input type="number" step="1" min="0" class="widefat" name="aksrt_credits_vip2" id="aksrt_credits_vip2" value="<?php echo esc_attr($credits_vip2_set ? (string) (int) $credits_vip2 : ''); ?>" placeholder="<?php esc_attr_e('留空=不设;0=免费', 'aksrt'); ?>">
|
|
</p>
|
|
<p class="aksrt-topic-meta-pay-fields">
|
|
<label for="aksrt_pay_price"><strong><?php esc_html_e('付费价格(元)', 'aksrt'); ?></strong></label>
|
|
<input type="number" step="0.01" min="0" class="widefat" name="aksrt_pay_price" id="aksrt_pay_price" value="<?php echo esc_attr((string) $pay_price); ?>">
|
|
</p>
|
|
<p class="aksrt-topic-meta-pay-fields">
|
|
<label for="aksrt_pay_vip1_price"><strong><?php esc_html_e('VIP1 价格(元)', 'aksrt'); ?></strong></label>
|
|
<input type="number" step="0.01" min="0" class="widefat" name="aksrt_pay_vip1_price" id="aksrt_pay_vip1_price" value="<?php echo esc_attr($vip1_set ? (string) (float) $pay_vip1_price : ''); ?>" placeholder="<?php esc_attr_e('留空=不设 VIP1 价', 'aksrt'); ?>">
|
|
</p>
|
|
<p class="aksrt-topic-meta-pay-fields">
|
|
<label for="aksrt_pay_vip2_price"><strong><?php esc_html_e('VIP2 价格(元)', 'aksrt'); ?></strong></label>
|
|
<input type="number" step="0.01" min="0" class="widefat" name="aksrt_pay_vip2_price" id="aksrt_pay_vip2_price" value="<?php echo esc_attr($vip2_set ? (string) (float) $pay_vip2_price : ''); ?>" placeholder="<?php esc_attr_e('留空=不设;0=VIP2 免费', 'aksrt'); ?>">
|
|
<span class="description"><?php esc_html_e('兼容旧「VIP 价格」字段:未填 VIP1 时仍可读 _aksrt_pay_vip_price。', 'aksrt'); ?></span>
|
|
</p>
|
|
<p class="aksrt-topic-meta-pay-hide-fields">
|
|
<label>
|
|
<input type="checkbox" name="aksrt_pay_hide_part" value="1" <?php checked($pay_hide_part); ?>>
|
|
<?php esc_html_e('仅隐藏部分内容', 'aksrt'); ?>
|
|
</label>
|
|
<span class="description" style="display:block;"><?php esc_html_e('开启后正文仍输出,付费块用 [payshow] 或 [hidecontent type="payshow"] 包裹。未开启则全文门控。', 'aksrt'); ?></span>
|
|
</p>
|
|
<?php
|
|
}
|
|
|
|
public function save(int $post_id, WP_Post $post): void {
|
|
if (!isset($_POST['aksrt_topic_meta_nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash((string) $_POST['aksrt_topic_meta_nonce'])), 'aksrt_topic_meta_save')) {
|
|
return;
|
|
}
|
|
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
|
|
return;
|
|
}
|
|
if ($post->post_type !== 'aksrt_forum_topic' || !current_user_can('edit_post', $post_id)) {
|
|
return;
|
|
}
|
|
|
|
$plate_id = isset($_POST['aksrt_plate_id']) ? max(0, (int) $_POST['aksrt_plate_id']) : 0;
|
|
update_post_meta($post_id, '_aksrt_plate_id', $plate_id);
|
|
|
|
$sticky = isset($_POST['aksrt_sticky']) ? max(0, min(3, (int) $_POST['aksrt_sticky'])) : 0;
|
|
update_post_meta($post_id, '_aksrt_sticky', $sticky);
|
|
update_post_meta($post_id, '_aksrt_locked', !empty($_POST['aksrt_locked']) ? 1 : 0);
|
|
update_post_meta($post_id, '_aksrt_featured', !empty($_POST['aksrt_featured']) ? 1 : 0);
|
|
|
|
$review = isset($_POST['aksrt_review_status']) ? sanitize_key((string) wp_unslash($_POST['aksrt_review_status'])) : 'approved';
|
|
if (!in_array($review, ['approved', 'pending', 'rejected'], true)) {
|
|
$review = 'approved';
|
|
}
|
|
update_post_meta($post_id, '_aksrt_review_status', $review);
|
|
|
|
$bbs_type = isset($_POST['aksrt_bbs_type']) ? sanitize_key((string) wp_unslash($_POST['aksrt_bbs_type'])) : 'standard';
|
|
if (!in_array($bbs_type, ['standard', 'question'], true)) {
|
|
$bbs_type = 'standard';
|
|
}
|
|
update_post_meta($post_id, '_aksrt_bbs_type', $bbs_type);
|
|
|
|
$this->save_visibility_meta($post_id);
|
|
|
|
// Keep board taxonomy bridge roughly in sync when plate selected.
|
|
if ($plate_id > 0 && taxonomy_exists('aksrt_forum_board')) {
|
|
$slug = 'plate-' . $plate_id;
|
|
$term = get_term_by('slug', $slug, 'aksrt_forum_board');
|
|
if (!$term) {
|
|
$title = get_the_title($plate_id) ?: $slug;
|
|
$created = wp_insert_term($title, 'aksrt_forum_board', ['slug' => $slug]);
|
|
if (!is_wp_error($created) && !empty($created['term_id'])) {
|
|
$term_id = (int) $created['term_id'];
|
|
} else {
|
|
$term_id = 0;
|
|
}
|
|
} else {
|
|
$term_id = (int) $term->term_id;
|
|
}
|
|
if ($term_id > 0) {
|
|
wp_set_object_terms($post_id, [$term_id], 'aksrt_forum_board', false);
|
|
}
|
|
}
|
|
}
|
|
|
|
private function save_visibility_meta(int $post_id): void {
|
|
$visibility = isset($_POST['aksrt_visibility']) ? sanitize_key((string) wp_unslash($_POST['aksrt_visibility'])) : 'public';
|
|
if (!in_array($visibility, self::VISIBILITY, true)) {
|
|
$visibility = 'public';
|
|
}
|
|
update_post_meta($post_id, '_aksrt_visibility', $visibility);
|
|
|
|
// Rewrite legacy dual allow_view so admin/runtime stay on one contract.
|
|
$allow_view_map = [
|
|
'public' => 'public',
|
|
'login' => 'login',
|
|
'comment' => 'login',
|
|
'role' => 'public',
|
|
'credits' => 'pay',
|
|
'pay' => 'pay',
|
|
'moderator' => 'moderator',
|
|
];
|
|
update_post_meta($post_id, '_aksrt_allow_view', $allow_view_map[$visibility] ?? 'public');
|
|
|
|
// Roles.
|
|
if ($visibility === 'role') {
|
|
$roles = $this->sanitize_role_list($_POST['aksrt_visibility_roles'] ?? []);
|
|
update_post_meta($post_id, '_aksrt_visibility_roles', $roles);
|
|
} else {
|
|
delete_post_meta($post_id, '_aksrt_visibility_roles');
|
|
}
|
|
|
|
// Credits (canonical + legacy dual-write).
|
|
if ($visibility === 'credits') {
|
|
$cost = isset($_POST['aksrt_credits_cost']) ? max(0, (int) wp_unslash($_POST['aksrt_credits_cost'])) : 0;
|
|
// Accept legacy field name if UI still posts it.
|
|
if ($cost <= 0 && isset($_POST['aksrt_pay_credits'])) {
|
|
$cost = max(0, (int) wp_unslash($_POST['aksrt_pay_credits']));
|
|
}
|
|
update_post_meta($post_id, '_aksrt_credits_cost', $cost);
|
|
update_post_meta($post_id, '_aksrt_pay_credits', $cost);
|
|
|
|
if (isset($_POST['aksrt_credits_vip1']) && (string) wp_unslash($_POST['aksrt_credits_vip1']) !== '') {
|
|
update_post_meta($post_id, '_aksrt_credits_vip1', max(0, (int) wp_unslash($_POST['aksrt_credits_vip1'])));
|
|
} else {
|
|
delete_post_meta($post_id, '_aksrt_credits_vip1');
|
|
}
|
|
if (isset($_POST['aksrt_credits_vip2']) && (string) wp_unslash($_POST['aksrt_credits_vip2']) !== '') {
|
|
update_post_meta($post_id, '_aksrt_credits_vip2', max(0, (int) wp_unslash($_POST['aksrt_credits_vip2'])));
|
|
} else {
|
|
delete_post_meta($post_id, '_aksrt_credits_vip2');
|
|
}
|
|
} else {
|
|
delete_post_meta($post_id, '_aksrt_credits_cost');
|
|
delete_post_meta($post_id, '_aksrt_pay_credits');
|
|
delete_post_meta($post_id, '_aksrt_credits_vip1');
|
|
delete_post_meta($post_id, '_aksrt_credits_vip2');
|
|
}
|
|
|
|
// Cash pay.
|
|
if ($visibility === 'pay') {
|
|
$price = isset($_POST['aksrt_pay_price']) ? max(0, (float) wp_unslash($_POST['aksrt_pay_price'])) : 0;
|
|
update_post_meta($post_id, '_aksrt_pay_price', $price);
|
|
|
|
$vip1_written = false;
|
|
if (isset($_POST['aksrt_pay_vip1_price']) && (string) wp_unslash($_POST['aksrt_pay_vip1_price']) !== '') {
|
|
$vip1 = max(0, (float) wp_unslash($_POST['aksrt_pay_vip1_price']));
|
|
update_post_meta($post_id, '_aksrt_pay_vip1_price', $vip1);
|
|
// Keep legacy single VIP key in sync with VIP1 for older readers.
|
|
update_post_meta($post_id, '_aksrt_pay_vip_price', $vip1);
|
|
$vip1_written = true;
|
|
} else {
|
|
delete_post_meta($post_id, '_aksrt_pay_vip1_price');
|
|
}
|
|
if (isset($_POST['aksrt_pay_vip2_price']) && (string) wp_unslash($_POST['aksrt_pay_vip2_price']) !== '') {
|
|
update_post_meta($post_id, '_aksrt_pay_vip2_price', max(0, (float) wp_unslash($_POST['aksrt_pay_vip2_price'])));
|
|
} else {
|
|
delete_post_meta($post_id, '_aksrt_pay_vip2_price');
|
|
}
|
|
// Legacy field if posted without vip1.
|
|
if (!$vip1_written && isset($_POST['aksrt_pay_vip_price']) && (string) wp_unslash($_POST['aksrt_pay_vip_price']) !== '') {
|
|
$vip = max(0, (float) wp_unslash($_POST['aksrt_pay_vip_price']));
|
|
update_post_meta($post_id, '_aksrt_pay_vip_price', $vip);
|
|
update_post_meta($post_id, '_aksrt_pay_vip1_price', $vip);
|
|
} elseif (!$vip1_written) {
|
|
delete_post_meta($post_id, '_aksrt_pay_vip_price');
|
|
}
|
|
} else {
|
|
delete_post_meta($post_id, '_aksrt_pay_price');
|
|
delete_post_meta($post_id, '_aksrt_pay_vip_price');
|
|
delete_post_meta($post_id, '_aksrt_pay_vip1_price');
|
|
delete_post_meta($post_id, '_aksrt_pay_vip2_price');
|
|
}
|
|
|
|
// Partial hide only meaningful for pay/credits; otherwise clear.
|
|
if (in_array($visibility, ['pay', 'credits'], true)) {
|
|
update_post_meta($post_id, '_aksrt_pay_hide_part', !empty($_POST['aksrt_pay_hide_part']) ? 1 : 0);
|
|
} else {
|
|
delete_post_meta($post_id, '_aksrt_pay_hide_part');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param mixed $raw
|
|
* @return string[]
|
|
*/
|
|
private function sanitize_role_list($raw): array {
|
|
if (!is_array($raw)) {
|
|
return [];
|
|
}
|
|
$allowed = array_keys($this->wp_role_options());
|
|
$out = [];
|
|
foreach ($raw as $role) {
|
|
$role = sanitize_key((string) $role);
|
|
if ($role !== '' && in_array($role, $allowed, true)) {
|
|
$out[] = $role;
|
|
}
|
|
}
|
|
return array_values(array_unique($out));
|
|
}
|
|
|
|
/**
|
|
* @return array<string, string>
|
|
*/
|
|
private function wp_role_options(): array {
|
|
if (!function_exists('wp_roles')) {
|
|
return [];
|
|
}
|
|
$roles = wp_roles();
|
|
if (!$roles || !is_array($roles->roles)) {
|
|
return [];
|
|
}
|
|
$out = [];
|
|
foreach ($roles->roles as $key => $data) {
|
|
$out[sanitize_key((string) $key)] = (string) ($data['name'] ?? $key);
|
|
}
|
|
return $out;
|
|
}
|
|
}
|