300 lines
11 KiB
PHP
300 lines
11 KiB
PHP
<?php
|
|
/**
|
|
* Unified frontend avatar helpers.
|
|
*
|
|
* @package AKSRT
|
|
*/
|
|
|
|
defined('ABSPATH') || exit;
|
|
|
|
if (!function_exists('aksrt_resolve_avatar_user_id')) {
|
|
/**
|
|
* Resolve a WordPress user ID from an avatar subject.
|
|
*
|
|
* @param mixed $subject User ID, WP_User, comment, post, or email.
|
|
*/
|
|
function aksrt_resolve_avatar_user_id($subject): int {
|
|
if ($subject instanceof WP_User) {
|
|
return (int) $subject->ID;
|
|
}
|
|
if ($subject instanceof WP_Post) {
|
|
return (int) $subject->post_author;
|
|
}
|
|
if ($subject instanceof WP_Comment) {
|
|
return (int) $subject->user_id;
|
|
}
|
|
if (is_object($subject) && isset($subject->user_id)) {
|
|
return (int) $subject->user_id;
|
|
}
|
|
if (is_numeric($subject)) {
|
|
return max(0, (int) $subject);
|
|
}
|
|
if (is_string($subject) && is_email($subject)) {
|
|
$user = get_user_by('email', $subject);
|
|
return $user instanceof WP_User ? (int) $user->ID : 0;
|
|
}
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (!function_exists('aksrt_get_default_avatar_url')) {
|
|
/**
|
|
* Return the bundled avatar used when a user has no custom image.
|
|
*/
|
|
function aksrt_get_default_avatar_url(): string {
|
|
return (string) apply_filters(
|
|
'aksrt_default_avatar_url',
|
|
get_theme_file_uri('assets/img/user/default avatar.svg')
|
|
);
|
|
}
|
|
}
|
|
|
|
if (!function_exists('aksrt_get_custom_avatar_url')) {
|
|
/**
|
|
* Return the locally uploaded avatar URL.
|
|
*/
|
|
function aksrt_get_custom_avatar_url(int $user_id, int $size = 96): string {
|
|
if ($user_id <= 0) {
|
|
return '';
|
|
}
|
|
|
|
$attachment_id = (int) get_user_meta($user_id, 'aksrt_avatar_attachment_id', true);
|
|
if ($attachment_id <= 0) {
|
|
return '';
|
|
}
|
|
|
|
$attachment = get_post($attachment_id);
|
|
if (!$attachment instanceof WP_Post || $attachment->post_type !== 'attachment' || (int) $attachment->post_author !== $user_id || !wp_attachment_is_image($attachment_id)) {
|
|
return '';
|
|
}
|
|
|
|
$image = wp_get_attachment_image_url($attachment_id, [$size, $size]);
|
|
return is_string($image) ? $image : '';
|
|
}
|
|
}
|
|
|
|
if (!function_exists('aksrt_get_avatar_url')) {
|
|
/**
|
|
* Resolve the canonical avatar URL used by all AKSRT frontend surfaces.
|
|
*
|
|
* @param mixed $subject User subject accepted by get_avatar_url().
|
|
* @param array|int $args WordPress avatar args or a numeric size.
|
|
*/
|
|
function aksrt_get_avatar_url($subject, $args = []): string {
|
|
if (is_numeric($args)) {
|
|
$args = ['size' => (int) $args];
|
|
}
|
|
$args = is_array($args) ? $args : [];
|
|
$size = max(16, min(512, (int) ($args['size'] ?? 96)));
|
|
$user_id = aksrt_resolve_avatar_user_id($subject);
|
|
$custom = aksrt_get_custom_avatar_url($user_id, $size);
|
|
if ($custom !== '') {
|
|
return $custom;
|
|
}
|
|
return aksrt_get_default_avatar_url();
|
|
}
|
|
}
|
|
|
|
if (!function_exists('aksrt_get_avatar_html')) {
|
|
/**
|
|
* Render a canonical avatar image.
|
|
*
|
|
* @param mixed $subject User subject.
|
|
* @param int $size Pixel size.
|
|
* @param array<string,mixed> $attrs Additional safe image attributes.
|
|
*/
|
|
function aksrt_get_avatar_html($subject, int $size = 96, array $attrs = []): string {
|
|
$size = max(16, min(512, $size));
|
|
$url = aksrt_get_avatar_url($subject, ['size' => $size]);
|
|
if ($url === '') {
|
|
return '';
|
|
}
|
|
|
|
$defaults = [
|
|
'class' => 'avatar avatar-' . $size . ' photo',
|
|
'alt' => '',
|
|
'width' => (string) $size,
|
|
'height' => (string) $size,
|
|
'loading' => 'lazy',
|
|
];
|
|
$attrs = array_merge($defaults, $attrs);
|
|
$html_attrs = '';
|
|
foreach ($attrs as $name => $value) {
|
|
$name = sanitize_key((string) $name);
|
|
if ($name === '' || $value === false || $value === null) {
|
|
continue;
|
|
}
|
|
$html_attrs .= ' ' . $name . '="' . esc_attr((string) $value) . '"';
|
|
}
|
|
return '<img src="' . esc_url($url) . '"' . $html_attrs . ' />';
|
|
}
|
|
}
|
|
|
|
add_filter('get_avatar_data', static function (array $args, $subject): array {
|
|
$user_id = aksrt_resolve_avatar_user_id($subject);
|
|
$custom = aksrt_get_custom_avatar_url($user_id, (int) ($args['size'] ?? 96));
|
|
$args['url'] = $custom !== '' ? $custom : aksrt_get_default_avatar_url();
|
|
$args['found_avatar'] = true;
|
|
return $args;
|
|
}, 20, 2);
|
|
|
|
add_action('show_user_profile', 'aksrt_render_admin_avatar_field');
|
|
add_action('edit_user_profile', 'aksrt_render_admin_avatar_field');
|
|
add_action('user_edit_form_tag', static function (): void {
|
|
echo ' enctype="multipart/form-data"';
|
|
});
|
|
|
|
if (!function_exists('aksrt_is_avatar_upload_enabled')) {
|
|
/**
|
|
* Whether local avatar uploads are enabled by attachment policy.
|
|
*/
|
|
function aksrt_is_avatar_upload_enabled(): bool {
|
|
$settings = class_exists('\\AKSRT\\Settings\\Settings') ? \AKSRT\Settings\Settings::get_all() : [];
|
|
return !isset($settings['allow_avatar_upload']) || !empty($settings['allow_avatar_upload']);
|
|
}
|
|
}
|
|
|
|
if (!function_exists('aksrt_render_admin_avatar_field')) {
|
|
/**
|
|
* Render local avatar controls on the native WordPress user profile screen.
|
|
*/
|
|
function aksrt_render_admin_avatar_field(WP_User $user): void {
|
|
if (!current_user_can('edit_user', $user->ID) || !aksrt_is_avatar_upload_enabled()) {
|
|
return;
|
|
}
|
|
?>
|
|
<h2><?php esc_html_e('AKSRT 头像', 'aksrt'); ?></h2>
|
|
<table class="form-table" role="presentation">
|
|
<tr>
|
|
<th scope="row"><?php esc_html_e('当前头像', 'aksrt'); ?></th>
|
|
<td>
|
|
<?php echo aksrt_get_avatar_html($user->ID, 96, ['loading' => 'eager']); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
|
<p><input type="file" name="aksrt_avatar_file" accept="image/jpeg,image/png,image/webp,image/gif"></p>
|
|
<?php if (aksrt_get_custom_avatar_url((int) $user->ID, 96) !== '') : ?>
|
|
<label><input type="checkbox" name="aksrt_avatar_reset" value="1"> <?php esc_html_e('恢复项目默认头像并删除当前头像文件', 'aksrt'); ?></label>
|
|
<?php endif; ?>
|
|
<p class="description"><?php esc_html_e('支持 JPG、PNG、GIF、WebP;大小上限由“权限 → 附件权限”控制。', 'aksrt'); ?></p>
|
|
<?php wp_nonce_field('aksrt_save_admin_avatar_' . $user->ID, 'aksrt_admin_avatar_nonce'); ?>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
<?php
|
|
}
|
|
}
|
|
|
|
add_filter('user_profile_update_errors', static function (WP_Error $errors, bool $update, WP_User $user): void {
|
|
if (empty($_FILES['aksrt_avatar_file']['name']) || !aksrt_is_avatar_upload_enabled()) {
|
|
return;
|
|
}
|
|
|
|
$file = $_FILES['aksrt_avatar_file'];
|
|
$settings = class_exists('\\AKSRT\\Settings\\Settings') ? \AKSRT\Settings\Settings::get_all() : [];
|
|
$max_size_mb = max(1, min(10, (int) ($settings['avatar_max_size'] ?? 2)));
|
|
$actual_size = !empty($file['tmp_name']) && is_readable($file['tmp_name']) ? (int) filesize($file['tmp_name']) : 0;
|
|
if ((int) ($file['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK || $actual_size <= 0 || $actual_size > $max_size_mb * MB_IN_BYTES) {
|
|
$errors->add('aksrt_avatar_size', sprintf(__('头像文件无效或超过 %dMB', 'aksrt'), $max_size_mb));
|
|
return;
|
|
}
|
|
|
|
require_once ABSPATH . 'wp-admin/includes/file.php';
|
|
$checked = wp_check_filetype_and_ext($file['tmp_name'], $file['name'], [
|
|
'jpg|jpeg|jpe' => 'image/jpeg',
|
|
'png' => 'image/png',
|
|
'gif' => 'image/gif',
|
|
'webp' => 'image/webp',
|
|
]);
|
|
$image_size = wp_getimagesize($file['tmp_name']);
|
|
if (empty($checked['type']) || empty($checked['ext']) || !wp_match_mime_types('image', $checked['type']) || !$image_size || ((int) $image_size[0] * (int) $image_size[1]) > 40000000) {
|
|
$errors->add('aksrt_avatar_type', __('头像必须是有效的 JPG、PNG、GIF 或 WebP 图片,且总像素不能超过 4000 万。', 'aksrt'));
|
|
}
|
|
}, 10, 3);
|
|
|
|
add_action('personal_options_update', 'aksrt_save_admin_avatar_field');
|
|
add_action('edit_user_profile_update', 'aksrt_save_admin_avatar_field');
|
|
|
|
if (!function_exists('aksrt_save_admin_avatar_field')) {
|
|
/**
|
|
* Save local avatar changes from the native WordPress profile screen.
|
|
*/
|
|
function aksrt_save_admin_avatar_field(int $user_id): void {
|
|
if (!current_user_can('edit_user', $user_id) || !aksrt_is_avatar_upload_enabled()) {
|
|
return;
|
|
}
|
|
$nonce = isset($_POST['aksrt_admin_avatar_nonce']) ? sanitize_text_field(wp_unslash($_POST['aksrt_admin_avatar_nonce'])) : '';
|
|
if (!wp_verify_nonce($nonce, 'aksrt_save_admin_avatar_' . $user_id)) {
|
|
return;
|
|
}
|
|
|
|
$previous_id = (int) get_user_meta($user_id, 'aksrt_avatar_attachment_id', true);
|
|
if (!empty($_POST['aksrt_avatar_reset'])) {
|
|
delete_user_meta($user_id, 'aksrt_avatar_attachment_id');
|
|
aksrt_delete_owned_avatar_attachment($previous_id, $user_id);
|
|
do_action('aksrt_user_avatar_changed', $user_id, 0, '');
|
|
return;
|
|
}
|
|
|
|
if (empty($_FILES['aksrt_avatar_file']['name'])) {
|
|
return;
|
|
}
|
|
|
|
$settings = class_exists('\\AKSRT\\Settings\\Settings') ? \AKSRT\Settings\Settings::get_all() : [];
|
|
$max_size = max(1, min(10, (int) ($settings['avatar_max_size'] ?? 2))) * MB_IN_BYTES;
|
|
$file = $_FILES['aksrt_avatar_file'];
|
|
$actual_size = !empty($file['tmp_name']) && is_readable($file['tmp_name']) ? (int) filesize($file['tmp_name']) : 0;
|
|
if ((int) ($file['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK || $actual_size <= 0 || $actual_size > $max_size) {
|
|
return;
|
|
}
|
|
|
|
require_once ABSPATH . 'wp-admin/includes/file.php';
|
|
$checked = wp_check_filetype_and_ext($file['tmp_name'], $file['name'], [
|
|
'jpg|jpeg|jpe' => 'image/jpeg',
|
|
'png' => 'image/png',
|
|
'gif' => 'image/gif',
|
|
'webp' => 'image/webp',
|
|
]);
|
|
$image_size = wp_getimagesize($file['tmp_name']);
|
|
if (empty($checked['type']) || empty($checked['ext']) || !wp_match_mime_types('image', $checked['type']) || !$image_size || ((int) $image_size[0] * (int) $image_size[1]) > 40000000) {
|
|
return;
|
|
}
|
|
|
|
require_once ABSPATH . 'wp-admin/includes/media.php';
|
|
require_once ABSPATH . 'wp-admin/includes/image.php';
|
|
$attachment_id = media_handle_upload('aksrt_avatar_file', 0, [], [
|
|
'test_form' => false,
|
|
'mimes' => [
|
|
'jpg|jpeg|jpe' => 'image/jpeg',
|
|
'png' => 'image/png',
|
|
'gif' => 'image/gif',
|
|
'webp' => 'image/webp',
|
|
],
|
|
]);
|
|
if (is_wp_error($attachment_id) || !wp_attachment_is_image((int) $attachment_id)) {
|
|
if (!is_wp_error($attachment_id)) {
|
|
wp_delete_attachment((int) $attachment_id, true);
|
|
}
|
|
return;
|
|
}
|
|
|
|
$attachment_id = (int) $attachment_id;
|
|
wp_update_post(['ID' => $attachment_id, 'post_author' => $user_id]);
|
|
update_post_meta($attachment_id, '_aksrt_profile_media_user_id', $user_id);
|
|
update_post_meta($attachment_id, '_aksrt_profile_media_type', 'avatar');
|
|
update_user_meta($user_id, 'aksrt_avatar_attachment_id', $attachment_id);
|
|
aksrt_delete_owned_avatar_attachment($previous_id, $user_id);
|
|
do_action('aksrt_user_avatar_changed', $user_id, $attachment_id, (string) wp_get_attachment_image_url($attachment_id, 'thumbnail'));
|
|
}
|
|
}
|
|
|
|
if (!function_exists('aksrt_delete_owned_avatar_attachment')) {
|
|
/**
|
|
* Delete an avatar attachment only when it belongs to the target user.
|
|
*/
|
|
function aksrt_delete_owned_avatar_attachment(int $attachment_id, int $user_id): bool {
|
|
$attachment = $attachment_id > 0 ? get_post($attachment_id) : null;
|
|
if (!$attachment instanceof WP_Post || $attachment->post_type !== 'attachment' || (int) $attachment->post_author !== $user_id) {
|
|
return false;
|
|
}
|
|
return (bool) wp_delete_attachment($attachment_id, true);
|
|
}
|
|
}
|