<?php
declare(strict_types=1);
namespace MeinWeimar;
use WP_Error;
use WP_REST_Request;
use WP_REST_Response;
use WP_REST_Server;
final class AccountApi
{
public static function hooks(): void
{
add_action('rest_api_init', [self::class, 'register_routes']);
}
public static function register_routes(): void
{
register_rest_route('meinweimar/v1', '/me', [
[
'methods' => WP_REST_Server::READABLE,
'callback' => [self::class, 'me'],
'permission_callback' => [self::class, 'require_login'],
],
[
'methods' => WP_REST_Server::EDITABLE,
'callback' => [self::class, 'update_me'],
'permission_callback' => [self::class, 'require_login'],
],
]);
register_rest_route('meinweimar/v1', '/profiles/(?P<user_id>\d+)', [
'methods' => WP_REST_Server::READABLE,
'callback' => [self::class, 'profile'],
'permission_callback' => '__return_true',
]);
}
public static function require_login(): bool|WP_Error
{
return is_user_logged_in()
? true
: new WP_Error('meinweimar_auth_required', __('Anmeldung erforderlich.', 'meinweimar-core'), ['status' => 401]);
}
public static function me(): WP_REST_Response|WP_Error
{
return self::profile_data(get_current_user_id(), true);
}
public static function profile(WP_REST_Request $request): WP_REST_Response|WP_Error
{
$user_id = absint($request['user_id']);
if (!$user_id || !get_userdata($user_id)) {
return new WP_Error('meinweimar_profile_not_found', __('Profil nicht gefunden.', 'meinweimar-core'), ['status' => 404]);
}
$is_owner = is_user_logged_in() && get_current_user_id() === $user_id;
return self::profile_data($user_id, $is_owner || current_user_can('manage_options'));
}
public static function update_me(WP_REST_Request $request): WP_REST_Response|WP_Error
{
$user_id = get_current_user_id();
$payload = $request->get_json_params();
$payload = is_array($payload) ? $payload : $request->get_params();
$nickname = self::text($payload['nickname'] ?? '', 80);
$gender = self::text($payload['gender'] ?? '', 40);
$status = self::text($payload['status'] ?? '', 160);
$visibility = self::allowed($payload['profile_visibility'] ?? 'public', ['public', 'private']);
$avatar_visibility = self::allowed($payload['avatar_visibility'] ?? 'private', ['public', 'private']);
$locale = Settings::sanitize_locale($payload['locale'] ?? '');
$age = self::age($payload['age'] ?? '');
$interests = self::interests($payload['interests'] ?? []);
$bio = self::text($payload['bio'] ?? '', 1000);
$avatar_id = absint($payload['avatar_id'] ?? 0);
if ($avatar_id && (!wp_attachment_is_image($avatar_id) || (int) get_post_field('post_author', $avatar_id) !== $user_id && !current_user_can('manage_options'))) {
return new WP_Error('meinweimar_invalid_avatar', __('Das Profilbild ist ungültig.', 'meinweimar-core'), ['status' => 400]);
}
update_user_meta($user_id, 'mw_profile_nickname', $nickname);
update_user_meta($user_id, 'mw_profile_gender', $gender);
update_user_meta($user_id, 'mw_profile_status', $status);
update_user_meta($user_id, 'mw_profile_visibility', $visibility);
update_user_meta($user_id, 'mw_avatar_visibility', $avatar_visibility);
update_user_meta($user_id, 'mw_profile_locale', $locale);
update_user_meta($user_id, 'mw_profile_age', $age);
update_user_meta($user_id, 'mw_profile_interests', $interests);
update_user_meta($user_id, 'mw_profile_avatar_id', $avatar_id);
$updated = wp_update_user(['ID' => $user_id, 'description' => $bio]);
if (is_wp_error($updated)) {
return $updated;
}
return self::profile_data($user_id, true);
}
public static function profile_data(int $user_id, bool $private): WP_REST_Response
{
$user = get_userdata($user_id);
$profile_visibility = self::allowed(get_user_meta($user_id, 'mw_profile_visibility', true), ['public', 'private']);
if (!$private && 'private' === $profile_visibility) {
return new WP_REST_Response(['user_id' => $user_id, 'visible' => false]);
}
$avatar_id = absint(get_user_meta($user_id, 'mw_profile_avatar_id', true));
$avatar_visibility = self::allowed(get_user_meta($user_id, 'mw_avatar_visibility', true), ['public', 'private']);
$show_avatar = $private || 'public' === $avatar_visibility;
$interests = get_user_meta($user_id, 'mw_profile_interests', true);
$interests = is_array($interests) ? array_values(array_filter(array_map('sanitize_text_field', $interests))) : [];
return new WP_REST_Response([
'user_id' => $user_id,
'visible' => true,
'nickname' => self::text(get_user_meta($user_id, 'mw_profile_nickname', true), 80) ?: (string) $user->display_name,
'display_name' => (string) $user->display_name,
'bio' => $private ? (string) $user->description : wp_trim_words((string) $user->description, 50),
'gender' => self::text(get_user_meta($user_id, 'mw_profile_gender', true), 40),
'age' => self::age(get_user_meta($user_id, 'mw_profile_age', true)),
'interests' => $interests,
'status' => self::text(get_user_meta($user_id, 'mw_profile_status', true), 160),
'locale' => Settings::sanitize_locale(get_user_meta($user_id, 'mw_profile_locale', true)),
'avatar_url' => $show_avatar && $avatar_id ? (string) wp_get_attachment_image_url($avatar_id, 'thumbnail') : '',
'avatar_visible' => $show_avatar && (bool) $avatar_id,
'avatar_visibility' => $private ? $avatar_visibility : 'private',
'profile_visibility' => $private ? $profile_visibility : 'public',
]);
}
private static function text($value, int $max): string
{
if (!is_scalar($value)) {
return '';
}
$value = sanitize_textarea_field((string) $value);
return function_exists('mb_substr') ? mb_substr($value, 0, $max) : substr($value, 0, $max);
}
private static function age($value): int
{
if ('' === (string) $value || !is_numeric($value)) {
return 0;
}
$age = absint($value);
return $age >= 13 && $age <= 120 ? $age : 0;
}
private static function interests($value): array
{
if (!is_array($value)) {
return [];
}
$items = [];
foreach (array_slice($value, 0, 20) as $item) {
$item = self::text($item, 60);
if ($item) {
$items[] = $item;
}
}
return array_values(array_unique($items));
}
private static function allowed($value, array $allowed): string
{
$value = is_scalar($value) ? sanitize_text_field((string) $value) : '';
return in_array($value, $allowed, true) ? $value : $allowed[0];
}
}
