395 lines
14 KiB
PHP
Executable File
395 lines
14 KiB
PHP
Executable File
<?php
|
|
if (!defined('ABSPATH')) {
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* Beep_Media — handles image uploads, video URLs, and media rendering.
|
|
* Uses WordPress built-in media library (no extra server requirements).
|
|
*/
|
|
class Beep_Media {
|
|
|
|
const META_KEY_IMAGES = 'beep_images';
|
|
const META_KEY_VIDEO = 'beep_video_url';
|
|
|
|
private static $image_types = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
|
|
private static $max_images = 4;
|
|
private static $max_file_size = 10 * 1024 * 1024; // 10 MB
|
|
|
|
public static function init() {}
|
|
|
|
/**
|
|
* Handle a media upload from the REST API.
|
|
*
|
|
* @param int $post_id The beep post ID to attach media to.
|
|
* @param array $file $_FILES entry.
|
|
* @return array|WP_Error
|
|
*/
|
|
public static function handle_upload($post_id, $file) {
|
|
if (!is_array($file) || $file['error'] !== UPLOAD_ERR_OK) {
|
|
return new WP_Error('upload_error', 'Upload failed.');
|
|
}
|
|
if ($file['size'] > self::$max_file_size) {
|
|
return new WP_Error('file_too_large', 'File exceeds 10 MB limit.');
|
|
}
|
|
$mime = mime_content_type($file['tmp_name']);
|
|
if (!in_array($mime, self::$image_types, true)) {
|
|
return new WP_Error('invalid_type', 'Only JPEG, PNG, GIF, and WebP are allowed.');
|
|
}
|
|
|
|
$existing = self::get_images($post_id);
|
|
if (count($existing) >= self::$max_images) {
|
|
return new WP_Error('too_many', 'Maximum ' . self::$max_images . ' images per beep.');
|
|
}
|
|
|
|
require_once ABSPATH . 'wp-admin/includes/file.php';
|
|
require_once ABSPATH . 'wp-admin/includes/image.php';
|
|
require_once ABSPATH . 'wp-admin/includes/media.php';
|
|
|
|
$file_return = wp_handle_sideload($file, [
|
|
'test_form' => false,
|
|
'mimes' => [
|
|
'jpg|jpeg' => 'image/jpeg',
|
|
'png' => 'image/png',
|
|
'gif' => 'image/gif',
|
|
'webp' => 'image/webp',
|
|
],
|
|
]);
|
|
|
|
if (isset($file_return['error'])) {
|
|
return new WP_Error('sideload_error', $file_return['error']);
|
|
}
|
|
|
|
$attachment = [
|
|
'post_mime_type' => $file_return['type'],
|
|
'post_title' => preg_replace('/\.[^.]+$/', '', basename($file_return['file'])),
|
|
'post_content' => '',
|
|
'post_status' => 'inherit',
|
|
'guid' => $file_return['url'],
|
|
];
|
|
|
|
$attachment_id = wp_insert_attachment($attachment, $file_return['file'], $post_id);
|
|
if (is_wp_error($attachment_id)) {
|
|
return $attachment_id;
|
|
}
|
|
|
|
$attach_data = wp_generate_attachment_metadata($attachment_id, $file_return['file']);
|
|
wp_update_attachment_metadata($attachment_id, $attach_data);
|
|
|
|
add_post_meta($post_id, self::META_KEY_IMAGES, $attachment_id);
|
|
|
|
return [
|
|
'id' => $attachment_id,
|
|
'url' => wp_get_attachment_url($attachment_id),
|
|
'thumb' => self::get_image_url($attachment_id, 'thumbnail'),
|
|
'sizes' => self::get_image_sizes($attachment_id),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Remove a single image from a beep post.
|
|
*/
|
|
public static function remove_image($post_id, $attachment_id) {
|
|
$ids = self::get_images($post_id);
|
|
$ids = array_values(array_filter($ids, fn($id) => (int) $id !== (int) $attachment_id));
|
|
delete_post_meta($post_id, self::META_KEY_IMAGES);
|
|
foreach ($ids as $id) {
|
|
add_post_meta($post_id, self::META_KEY_IMAGES, $id);
|
|
}
|
|
wp_delete_attachment($attachment_id, true);
|
|
}
|
|
|
|
/**
|
|
* Get image attachment IDs for a beep post.
|
|
*/
|
|
public static function get_images($post_id) {
|
|
$ids = get_post_meta($post_id, self::META_KEY_IMAGES, false);
|
|
return array_filter(array_map('intval', (array) $ids));
|
|
}
|
|
|
|
/**
|
|
* Get image srcset-ready sizes for an attachment.
|
|
*/
|
|
public static function get_image_sizes($attachment_id) {
|
|
$sizes = [];
|
|
foreach (['thumbnail', 'medium', 'large', 'full'] as $size) {
|
|
$img = wp_get_attachment_image_src($attachment_id, $size);
|
|
if ($img) {
|
|
$sizes[$size] = ['url' => $img[0], 'width' => $img[1], 'height' => $img[2]];
|
|
}
|
|
}
|
|
return $sizes;
|
|
}
|
|
|
|
/**
|
|
* Get image URL at a given size.
|
|
*/
|
|
public static function get_image_url($attachment_id, $size = 'medium') {
|
|
$img = wp_get_attachment_image_src($attachment_id, $size);
|
|
return $img ? $img[0] : '';
|
|
}
|
|
|
|
/**
|
|
* Set a video URL for a beep post.
|
|
*/
|
|
public static function set_video_url($post_id, $url) {
|
|
$url = esc_url_raw($url);
|
|
if (!$url) return new WP_Error('invalid_url', 'Invalid video URL.');
|
|
update_post_meta($post_id, self::META_KEY_VIDEO, $url);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Get the video URL for a beep post.
|
|
*/
|
|
public static function get_video_url($post_id) {
|
|
return esc_url_raw(get_post_meta($post_id, self::META_KEY_VIDEO, true));
|
|
}
|
|
|
|
/**
|
|
* Detect if a URL is a supported video oEmbed.
|
|
*/
|
|
public static function detect_video_url($url) {
|
|
$patterns = [
|
|
'youtube' => '#https?://(?:www\.)?youtube\.com/watch\?v=[^\s&#]+#',
|
|
'yt_s' => '#https?://youtu\.be/[^\s&#]+#',
|
|
'vimeo' => '#https?://vimeo\.com/\d+#',
|
|
'twitter' => '#https?://(?:twitter\.com|x\.com)/[^\s#/]+/status/\d+#',
|
|
];
|
|
foreach ($patterns as $name => $pattern) {
|
|
if (preg_match($pattern, $url)) return $name;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Render the Twitter-style image gallery.
|
|
*
|
|
* @param int[] $image_ids
|
|
* @return string HTML
|
|
*/
|
|
public static function render_image_gallery($image_ids) {
|
|
if (empty($image_ids)) return '';
|
|
|
|
$count = count($image_ids);
|
|
$grid_class = 'beep-gallery';
|
|
if ($count === 1) $grid_class .= ' beep-gallery-1';
|
|
elseif ($count === 2) $grid_class .= ' beep-gallery-2';
|
|
elseif ($count === 3) $grid_class .= ' beep-gallery-3';
|
|
else $grid_class .= ' beep-gallery-4';
|
|
|
|
$html = '<div class="' . esc_attr($grid_class) . '">';
|
|
foreach ($image_ids as $i => $id) {
|
|
$full = self::get_image_url($id, 'large');
|
|
$thumb = self::get_image_url($id, 'medium');
|
|
if (!$thumb) $thumb = $full;
|
|
if (!$full) continue;
|
|
$alt = get_post_meta($id, '_wp_attachment_image_alt', true);
|
|
$html .= '<div class="beep-gallery-item">';
|
|
$html .= '<a href="' . esc_url($full) . '" target="_blank" rel="noopener" data-beep-lightbox>';
|
|
$html .= '<img src="' . esc_url($thumb) . '" alt="' . esc_attr($alt ?: "Beep image") . '" loading="lazy">';
|
|
$html .= '</a>';
|
|
$html .= '</div>';
|
|
}
|
|
$html .= '</div>';
|
|
return $html;
|
|
}
|
|
|
|
/**
|
|
* Render video embed via oEmbed.
|
|
*/
|
|
public static function render_video_embed($url) {
|
|
if (!$url) return '';
|
|
$embed = wp_oembed_get($url, ['width' => 560, 'height' => 315]);
|
|
if ($embed) {
|
|
return '<div class="beep-video-embed">' . $embed . '</div>';
|
|
}
|
|
return '';
|
|
}
|
|
|
|
/**
|
|
* Delete all media for a beep post (when beep is deleted).
|
|
*/
|
|
public static function delete_media($post_id) {
|
|
foreach (self::get_images($post_id) as $id) {
|
|
wp_delete_attachment($id, true);
|
|
}
|
|
delete_post_meta($post_id, self::META_KEY_IMAGES);
|
|
delete_post_meta($post_id, self::META_KEY_VIDEO);
|
|
}
|
|
// Voice note support
|
|
private static $audio_types = ['audio/mpeg', 'audio/ogg', 'audio/wav', 'audio/webm', 'audio/mp4', 'audio/x-m4a'];
|
|
private static $max_audio_size = 20 * 1024 * 1024; // 20 MB
|
|
|
|
public static function handle_voice_upload($post_id, $file) {
|
|
if (!is_array($file) || $file['error'] !== UPLOAD_ERR_OK) {
|
|
return new WP_Error('upload_error', 'Upload failed.');
|
|
}
|
|
if ($file['size'] > self::$max_audio_size) {
|
|
return new WP_Error('file_too_large', 'Voice note exceeds 20 MB limit.');
|
|
}
|
|
$mime = mime_content_type($file['tmp_name']);
|
|
if (!in_array($mime, self::$audio_types, true)) {
|
|
return new WP_Error('invalid_type', 'Only MP3, OGG, WAV, WebM, and M4A audio are allowed.');
|
|
}
|
|
|
|
require_once ABSPATH . 'wp-admin/includes/file.php';
|
|
require_once ABSPATH . 'wp-admin/includes/media.php';
|
|
|
|
$file_return = wp_handle_upload($file, [
|
|
'test_form' => false,
|
|
'mimes' => [
|
|
'mp3' => 'audio/mpeg',
|
|
'ogg' => 'audio/ogg',
|
|
'wav' => 'audio/wav',
|
|
'webm' => 'audio/webm',
|
|
'm4a' => 'audio/mp4',
|
|
],
|
|
]);
|
|
|
|
if (isset($file_return['error'])) {
|
|
return new WP_Error('upload_error', $file_return['error']);
|
|
}
|
|
|
|
$attachment = [
|
|
'post_mime_type' => $file_return['type'],
|
|
'post_title' => preg_replace('/\.[^.]+$/', '', basename($file_return['file'])),
|
|
'post_content' => '',
|
|
'post_status' => 'inherit',
|
|
'guid' => $file_return['url'],
|
|
];
|
|
|
|
$attachment_id = wp_insert_attachment($attachment, $file_return['file'], $post_id);
|
|
if (is_wp_error($attachment_id)) {
|
|
return $attachment_id;
|
|
}
|
|
|
|
$attach_data = wp_generate_attachment_metadata($attachment_id, $file_return['file']);
|
|
wp_update_attachment_metadata($attachment_id, $attach_data);
|
|
|
|
update_post_meta($post_id, 'beep_voice_url', wp_get_attachment_url($attachment_id));
|
|
update_post_meta($post_id, 'beep_voice_id', $attachment_id);
|
|
|
|
return [
|
|
'id' => $attachment_id,
|
|
'url' => wp_get_attachment_url($attachment_id),
|
|
];
|
|
}
|
|
|
|
public static function get_voice_url($post_id) {
|
|
return esc_url_raw(get_post_meta($post_id, 'beep_voice_url', true));
|
|
}
|
|
|
|
public static function remove_voice($post_id) {
|
|
$id = (int) get_post_meta($post_id, 'beep_voice_id', true);
|
|
if ($id) {
|
|
wp_delete_attachment($id, true);
|
|
}
|
|
delete_post_meta($post_id, 'beep_voice_url');
|
|
delete_post_meta($post_id, 'beep_voice_id');
|
|
}
|
|
|
|
public static function render_voice_note($post_id) {
|
|
$url = self::get_voice_url($post_id);
|
|
if (!$url) return '';
|
|
$duration = '';
|
|
$meta = get_post_meta($post_id, 'beep_voice_id', true);
|
|
if ($meta) {
|
|
$metadata = wp_get_attachment_metadata($meta);
|
|
if ($metadata && isset($metadata['length_formatted'])) {
|
|
$duration = $metadata['length_formatted'];
|
|
}
|
|
}
|
|
$dur_html = $duration ? '<span class="beep-voice-duration">' . esc_html($duration) . '</span>' : '';
|
|
return '<div class="beep-voice-note">' .
|
|
'<audio controls preload="none">' .
|
|
'<source src="' . esc_url($url) . '">' .
|
|
'Your browser does not support audio playback.' .
|
|
'</audio>' .
|
|
'<div class="beep-voice-label"><svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor"><path d="M12 14c1.66 0 3-1.34 3-3V5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3zm-1-9c0-.55.45-1 1-1s1 .45 1 1v6c0 .55-.45 1-1 1s-1-.45-1-1V5zm6 6c0 2.76-2.24 5-5 5s-5-2.24-5-5H5c0 3.53 2.61 6.43 6 6.92V21h2v-3.08C16.39 17.43 19 14.53 19 11h-2z"/></svg>Voice note</div>' .
|
|
$dur_html .
|
|
'</div>';
|
|
}
|
|
|
|
// Video file upload support
|
|
private static $video_types = ['video/mp4', 'video/webm', 'video/ogg', 'video/quicktime'];
|
|
private static $max_video_size = 100 * 1024 * 1024; // 100 MB
|
|
|
|
public static function handle_video_upload($post_id, $file) {
|
|
if (!is_array($file) || $file['error'] !== UPLOAD_ERR_OK) {
|
|
return new WP_Error('upload_error', 'Upload failed.');
|
|
}
|
|
if ($file['size'] > self::$max_video_size) {
|
|
return new WP_Error('file_too_large', 'Video exceeds 100 MB limit.');
|
|
}
|
|
$mime = mime_content_type($file['tmp_name']);
|
|
if (!in_array($mime, self::$video_types, true)) {
|
|
return new WP_Error('invalid_type', 'Only MP4, WebM, OGG, and MOV are allowed.');
|
|
}
|
|
|
|
require_once ABSPATH . 'wp-admin/includes/file.php';
|
|
require_once ABSPATH . 'wp-admin/includes/media.php';
|
|
|
|
$file_return = wp_handle_upload($file, [
|
|
'test_form' => false,
|
|
'mimes' => [
|
|
'mp4' => 'video/mp4',
|
|
'webm' => 'video/webm',
|
|
'ogg' => 'video/ogg',
|
|
'mov' => 'video/quicktime',
|
|
],
|
|
]);
|
|
|
|
if (isset($file_return['error'])) {
|
|
return new WP_Error('upload_error', $file_return['error']);
|
|
}
|
|
|
|
$attachment = [
|
|
'post_mime_type' => $file_return['type'],
|
|
'post_title' => preg_replace('/\.[^.]+$/', '', basename($file_return['file'])),
|
|
'post_content' => '',
|
|
'post_status' => 'inherit',
|
|
'guid' => $file_return['url'],
|
|
];
|
|
|
|
$attachment_id = wp_insert_attachment($attachment, $file_return['file'], $post_id);
|
|
if (is_wp_error($attachment_id)) {
|
|
return $attachment_id;
|
|
}
|
|
|
|
$attach_data = wp_generate_attachment_metadata($attachment_id, $file_return['file']);
|
|
wp_update_attachment_metadata($attachment_id, $attach_data);
|
|
|
|
update_post_meta($post_id, 'beep_video_url', wp_get_attachment_url($attachment_id));
|
|
update_post_meta($post_id, 'beep_video_id', $attachment_id);
|
|
|
|
return [
|
|
'id' => $attachment_id,
|
|
'url' => wp_get_attachment_url($attachment_id),
|
|
];
|
|
}
|
|
|
|
public static function get_upload_video_url($post_id) {
|
|
return esc_url_raw(get_post_meta($post_id, 'beep_video_url', true));
|
|
}
|
|
|
|
public static function remove_video($post_id) {
|
|
$id = (int) get_post_meta($post_id, 'beep_video_id', true);
|
|
if ($id) {
|
|
wp_delete_attachment($id, true);
|
|
}
|
|
delete_post_meta($post_id, 'beep_video_url');
|
|
delete_post_meta($post_id, 'beep_video_id');
|
|
}
|
|
|
|
public static function render_video_player($post_id) {
|
|
$url = self::get_upload_video_url($post_id);
|
|
if (!$url) return '';
|
|
return '<div class="beep-video-player">' .
|
|
'<video controls preload="metadata" playsinline>' .
|
|
'<source src="' . esc_url($url) . '">' .
|
|
'Your browser does not support video playback.' .
|
|
'</video></div>';
|
|
}
|
|
|
|
} |