248 lines
8.1 KiB
PHP
248 lines
8.1 KiB
PHP
<?php
|
||
/**
|
||
* Plugin Name: Fe Adulta — API subir avatar
|
||
* Description: Endpoint REST acotado para asignar la foto de perfil de un autor.
|
||
* Version: 1.0
|
||
*
|
||
* POST /wp-json/fea/v1/subir-avatar
|
||
* multipart/form-data: user_id=<id>, avatar=<imagen JPEG|PNG|WebP>
|
||
*
|
||
* Ver issue gitea.feadulta.com/rafa/feadulta#175.
|
||
*/
|
||
|
||
if (!defined('ABSPATH')) exit;
|
||
|
||
const FEA_AVATAR_MAX_BYTES = 5242880; // 5 MiB.
|
||
const FEA_AVATAR_SIZE = 512;
|
||
const FEA_AVATAR_MIN_SIZE = 512;
|
||
const FEA_AVATAR_MAX_DIMENSION = 4096;
|
||
const FEA_AVATAR_MAX_PIXELS = 16777216; // 16 megapíxeles.
|
||
|
||
add_action('rest_api_init', function () {
|
||
register_rest_route('fea/v1', '/subir-avatar', [
|
||
'methods' => WP_REST_Server::CREATABLE,
|
||
'callback' => 'fea_subir_avatar_handle',
|
||
'permission_callback' => 'fea_subir_avatar_can_call',
|
||
// Se valida dentro del handler, después del permission_callback: una
|
||
// llamada sin autenticar siempre recibe 401, incluso si omite user_id.
|
||
'args' => [
|
||
'user_id' => [
|
||
'sanitize_callback' => 'absint',
|
||
],
|
||
],
|
||
]);
|
||
});
|
||
|
||
/** Editor o superior: mismo nivel que el endpoint /crear-autor. */
|
||
function fea_subir_avatar_can_call(WP_REST_Request $request) {
|
||
if (!is_user_logged_in()) {
|
||
return new WP_Error(
|
||
'fea_subir_avatar_not_authenticated',
|
||
'Debes autenticarte para asignar un avatar.',
|
||
['status' => 401]
|
||
);
|
||
}
|
||
if (!current_user_can('edit_others_posts')) {
|
||
return new WP_Error(
|
||
'fea_subir_avatar_forbidden',
|
||
'No tienes permiso para asignar avatares.',
|
||
['status' => 403]
|
||
);
|
||
}
|
||
return true;
|
||
}
|
||
|
||
function fea_subir_avatar_handle(WP_REST_Request $request) {
|
||
$user_id = absint($request->get_param('user_id'));
|
||
if (!$user_id) {
|
||
return new WP_Error(
|
||
'fea_subir_avatar_invalid_user',
|
||
'user_id es obligatorio.',
|
||
['status' => 400]
|
||
);
|
||
}
|
||
|
||
$user = get_userdata($user_id);
|
||
if (!$user) {
|
||
return new WP_Error(
|
||
'fea_subir_avatar_user_not_found',
|
||
'No existe el usuario indicado.',
|
||
['status' => 404]
|
||
);
|
||
}
|
||
|
||
$files = $request->get_file_params();
|
||
$file = $files['avatar'] ?? null;
|
||
$validation = fea_subir_avatar_validate_file($file);
|
||
if (is_wp_error($validation)) return $validation;
|
||
|
||
require_once ABSPATH . 'wp-admin/includes/file.php';
|
||
require_once ABSPATH . 'wp-admin/includes/image.php';
|
||
|
||
$uploaded = wp_handle_upload($file, [
|
||
'test_form' => false,
|
||
'mimes' => [
|
||
'jpg|jpeg|jpe' => 'image/jpeg',
|
||
'png' => 'image/png',
|
||
'webp' => 'image/webp',
|
||
],
|
||
]);
|
||
if (!empty($uploaded['error'])) {
|
||
return new WP_Error(
|
||
'fea_subir_avatar_upload_failed',
|
||
'No se pudo guardar la imagen: ' . $uploaded['error'],
|
||
['status' => 400]
|
||
);
|
||
}
|
||
|
||
$normalized = fea_subir_avatar_normalize($uploaded['file']);
|
||
if (is_wp_error($normalized)) {
|
||
wp_delete_file($uploaded['file']);
|
||
return $normalized;
|
||
}
|
||
|
||
$attachment_id = wp_insert_attachment([
|
||
'post_mime_type' => $normalized['mime-type'],
|
||
'post_title' => 'Avatar — ' . $user->display_name,
|
||
'post_status' => 'inherit',
|
||
], $normalized['path'], 0, true);
|
||
if (is_wp_error($attachment_id)) {
|
||
wp_delete_file($normalized['path']);
|
||
return new WP_Error(
|
||
'fea_subir_avatar_attachment_failed',
|
||
'No se pudo crear el attachment del avatar.',
|
||
['status' => 500]
|
||
);
|
||
}
|
||
|
||
$metadata = wp_generate_attachment_metadata($attachment_id, $normalized['path']);
|
||
if (is_wp_error($metadata) || !is_array($metadata) || !$metadata) {
|
||
wp_delete_attachment($attachment_id, true);
|
||
return new WP_Error(
|
||
'fea_subir_avatar_metadata_failed',
|
||
'No se pudo generar la metadata del avatar.',
|
||
['status' => 500]
|
||
);
|
||
}
|
||
wp_update_attachment_metadata($attachment_id, $metadata);
|
||
if (!wp_get_attachment_metadata($attachment_id)) {
|
||
wp_delete_attachment($attachment_id, true);
|
||
return new WP_Error(
|
||
'fea_subir_avatar_metadata_failed',
|
||
'No se pudo guardar la metadata del avatar.',
|
||
['status' => 500]
|
||
);
|
||
}
|
||
|
||
$previous_attachment_id = (int) get_user_meta($user_id, 'foto_perfil', true);
|
||
if (!update_user_meta($user_id, 'foto_perfil', (string) $attachment_id)) {
|
||
wp_delete_attachment($attachment_id, true);
|
||
return new WP_Error(
|
||
'fea_subir_avatar_assignment_failed',
|
||
'No se pudo asignar el avatar al usuario.',
|
||
['status' => 500]
|
||
);
|
||
}
|
||
|
||
$size = wp_getimagesize($normalized['path']);
|
||
return new WP_REST_Response([
|
||
'user_id' => $user_id,
|
||
'attachment_id' => (int) $attachment_id,
|
||
'previous_attachment_id' => $previous_attachment_id ?: null,
|
||
'avatar_url' => wp_get_attachment_image_url($attachment_id, 'full'),
|
||
'width' => (int) ($size[0] ?? 0),
|
||
'height' => (int) ($size[1] ?? 0),
|
||
], 201);
|
||
}
|
||
|
||
function fea_subir_avatar_validate_file($file) {
|
||
if (!is_array($file) || empty($file['tmp_name']) || !isset($file['error'])) {
|
||
return new WP_Error(
|
||
'fea_subir_avatar_missing_file',
|
||
'avatar es obligatorio.',
|
||
['status' => 400]
|
||
);
|
||
}
|
||
if ((int) $file['error'] !== UPLOAD_ERR_OK) {
|
||
return new WP_Error(
|
||
'fea_subir_avatar_file_error',
|
||
'La subida de avatar falló.',
|
||
['status' => 400]
|
||
);
|
||
}
|
||
if ((int) $file['size'] > FEA_AVATAR_MAX_BYTES) {
|
||
return new WP_Error(
|
||
'fea_subir_avatar_file_too_large',
|
||
'La imagen no puede superar 5 MB.',
|
||
['status' => 400]
|
||
);
|
||
}
|
||
|
||
$image = wp_getimagesize($file['tmp_name']);
|
||
if (!$image) {
|
||
return new WP_Error(
|
||
'fea_subir_avatar_invalid_image',
|
||
'avatar debe ser una imagen válida.',
|
||
['status' => 400]
|
||
);
|
||
}
|
||
|
||
$width = (int) $image[0];
|
||
$height = (int) $image[1];
|
||
if ($width < FEA_AVATAR_MIN_SIZE || $height < FEA_AVATAR_MIN_SIZE) {
|
||
return new WP_Error(
|
||
'fea_subir_avatar_invalid_image',
|
||
'avatar debe ser una imagen de al menos 512×512 píxeles.',
|
||
['status' => 400]
|
||
);
|
||
}
|
||
if ($width > FEA_AVATAR_MAX_DIMENSION || $height > FEA_AVATAR_MAX_DIMENSION || ($width * $height) > FEA_AVATAR_MAX_PIXELS) {
|
||
return new WP_Error(
|
||
'fea_subir_avatar_image_too_large',
|
||
'avatar no puede superar 4096 píxeles por lado ni 16 megapíxeles.',
|
||
['status' => 400]
|
||
);
|
||
}
|
||
|
||
$allowed_mimes = ['image/jpeg', 'image/png', 'image/webp'];
|
||
if (!in_array($image['mime'], $allowed_mimes, true)) {
|
||
return new WP_Error(
|
||
'fea_subir_avatar_unsupported_type',
|
||
'Solo se admiten imágenes JPEG, PNG o WebP.',
|
||
['status' => 400]
|
||
);
|
||
}
|
||
return true;
|
||
}
|
||
|
||
/** Recorta al centro y normaliza la imagen al tamaño estándar del avatar. */
|
||
function fea_subir_avatar_normalize(string $path) {
|
||
$editor = wp_get_image_editor($path);
|
||
if (is_wp_error($editor)) {
|
||
return new WP_Error(
|
||
'fea_subir_avatar_editor_unavailable',
|
||
'No se pudo procesar la imagen subida.',
|
||
['status' => 400]
|
||
);
|
||
}
|
||
|
||
$resized = $editor->resize(FEA_AVATAR_SIZE, FEA_AVATAR_SIZE, true);
|
||
if (is_wp_error($resized)) {
|
||
return new WP_Error(
|
||
'fea_subir_avatar_resize_failed',
|
||
'No se pudo normalizar el avatar.',
|
||
['status' => 400]
|
||
);
|
||
}
|
||
|
||
$saved = $editor->save($path);
|
||
if (is_wp_error($saved) || empty($saved['path'])) {
|
||
return new WP_Error(
|
||
'fea_subir_avatar_save_failed',
|
||
'No se pudo guardar el avatar normalizado.',
|
||
['status' => 500]
|
||
);
|
||
}
|
||
return $saved;
|
||
}
|