v1.9.0: reply composer media support (images, GIF, voice, quotes, polls on replies)
This commit is contained in:
@@ -76,6 +76,7 @@ class Beep_Plugin {
|
||||
Beep_CPT::register();
|
||||
Beep_Likes::create_table();
|
||||
Beep_Polls::create_tables();
|
||||
Beep_Replies::create_tables();
|
||||
flush_rewrite_rules();
|
||||
}
|
||||
|
||||
|
||||
+181
-6
@@ -15,6 +15,10 @@ class Beep_Media {
|
||||
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
|
||||
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
|
||||
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 init() {}
|
||||
|
||||
@@ -217,9 +221,183 @@ class Beep_Media {
|
||||
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
|
||||
|
||||
/* ============================================================
|
||||
* Parent-agnostic wrappers (v1.9.0)
|
||||
* $parent_type = 'post' (default) or 'comment'.
|
||||
* Routes to post_meta or comment_meta automatically.
|
||||
* ============================================================ */
|
||||
|
||||
public static function images_meta_key() {
|
||||
return self::META_KEY_IMAGES;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the right meta storage for a parent (post or comment).
|
||||
*
|
||||
* @return array{get:string,add:string,del:string}|WP_Error
|
||||
*/
|
||||
private static function meta_store($parent_id, $parent_type = 'post') {
|
||||
if ($parent_type === 'comment') {
|
||||
return [
|
||||
'get' => 'get_comment_meta',
|
||||
'add' => 'add_comment_meta',
|
||||
'del' => 'delete_comment_meta',
|
||||
];
|
||||
}
|
||||
if ($parent_type === 'post') {
|
||||
return [
|
||||
'get' => 'get_post_meta',
|
||||
'add' => 'add_post_meta',
|
||||
'del' => 'delete_post_meta',
|
||||
];
|
||||
}
|
||||
return new WP_Error('invalid_parent', 'parent_type must be post or comment.');
|
||||
}
|
||||
|
||||
/** Image upload wrapper that works for posts or comments. */
|
||||
public static function handle_upload_for_parent($parent_id, $file, $parent_type = 'post') {
|
||||
$store = self::meta_store($parent_id, $parent_type);
|
||||
if (is_wp_error($store)) return $store;
|
||||
|
||||
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 = call_user_func($store['get'], $parent_id, self::META_KEY_IMAGES, false);
|
||||
$existing = array_filter(array_map('intval', (array) $existing));
|
||||
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'],
|
||||
];
|
||||
|
||||
// Attachments still need a post parent for WP core; use the comment's post ID when on a comment.
|
||||
$attach_parent = $parent_type === 'comment'
|
||||
? (int) get_comment($parent_id)->comment_post_ID
|
||||
: $parent_id;
|
||||
$attachment_id = wp_insert_attachment($attachment, $file_return['file'], $attach_parent);
|
||||
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);
|
||||
|
||||
call_user_func($store['add'], $parent_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),
|
||||
];
|
||||
}
|
||||
|
||||
/** Image removal wrapper. */
|
||||
public static function remove_image_from_parent($parent_id, $attachment_id, $parent_type = 'post') {
|
||||
$store = self::meta_store($parent_id, $parent_type);
|
||||
if (is_wp_error($store)) return $store;
|
||||
$ids = call_user_func($store['get'], $parent_id, self::META_KEY_IMAGES, false);
|
||||
$ids = array_values(array_filter((array) $ids, fn($id) => (int) $id !== (int) $attachment_id));
|
||||
call_user_func($store['del'], $parent_id, self::META_KEY_IMAGES);
|
||||
foreach ($ids as $id) {
|
||||
call_user_func($store['add'], $parent_id, self::META_KEY_IMAGES, $id);
|
||||
}
|
||||
wp_delete_attachment($attachment_id, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Voice upload wrapper. */
|
||||
public static function handle_voice_upload_for_parent($parent_id, $file, $parent_type = 'post') {
|
||||
$store = self::meta_store($parent_id, $parent_type);
|
||||
if (is_wp_error($store)) return $store;
|
||||
|
||||
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'],
|
||||
];
|
||||
|
||||
$attach_parent = $parent_type === 'comment'
|
||||
? (int) get_comment($parent_id)->comment_post_ID
|
||||
: $parent_id;
|
||||
$attachment_id = wp_insert_attachment($attachment, $file_return['file'], $attach_parent);
|
||||
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);
|
||||
|
||||
$url = wp_get_attachment_url($attachment_id);
|
||||
call_user_func($store['add'], $parent_id, 'beep_reply_voice_url', $url);
|
||||
call_user_func($store['add'], $parent_id, 'beep_reply_voice_id', $attachment_id);
|
||||
|
||||
return ['id' => $attachment_id, 'url' => $url];
|
||||
}
|
||||
|
||||
public static function handle_voice_upload($post_id, $file) {
|
||||
if (!is_array($file) || $file['error'] !== UPLOAD_ERR_OK) {
|
||||
@@ -312,9 +490,6 @@ class Beep_Media {
|
||||
}
|
||||
|
||||
// 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.');
|
||||
|
||||
+233
-1
@@ -3,10 +3,41 @@ if (!defined('ABSPATH')) { exit; }
|
||||
|
||||
class Beep_Replies {
|
||||
|
||||
/** Comment meta keys (mirrors Beep_Media + Beep_Quotes for posts). */
|
||||
const META_IMAGES = 'beep_reply_images';
|
||||
const META_VOICE = 'beep_reply_voice_url';
|
||||
const META_VOICE_ID = 'beep_reply_voice_id';
|
||||
const META_GIF = 'beep_reply_gif_url';
|
||||
const META_QUOTE_ID = 'beep_reply_quoted_post_id';
|
||||
|
||||
public static function init() {
|
||||
add_action('rest_api_init', [__CLASS__, 'register_routes']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the reply_polls table (v1.9.0).
|
||||
* Mirrors Beep_Polls::create_tables() but keyed by comment_id.
|
||||
*/
|
||||
public static function create_tables() {
|
||||
global $wpdb;
|
||||
$table = $wpdb->prefix . 'beep_reply_polls';
|
||||
$charset = $wpdb->get_charset_collate();
|
||||
$sql = "CREATE TABLE $table (
|
||||
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
comment_id BIGINT(20) UNSIGNED NOT NULL,
|
||||
question TEXT NOT NULL,
|
||||
options TEXT NOT NULL,
|
||||
votes TEXT NOT NULL DEFAULT '[]',
|
||||
total_votes BIGINT(20) UNSIGNED NOT NULL DEFAULT 0,
|
||||
ends_at DATETIME NULL,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
KEY comment_id (comment_id)
|
||||
) $charset;";
|
||||
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
|
||||
dbDelta($sql);
|
||||
}
|
||||
|
||||
public static function register_routes() {
|
||||
register_rest_route('beep/v1', '/post', [
|
||||
'methods' => 'POST',
|
||||
@@ -33,6 +64,38 @@ class Beep_Replies {
|
||||
'callback' => [__CLASS__, 'delete_reply'],
|
||||
'permission_callback' => ['Beep_Likes', 'can_interact'],
|
||||
]);
|
||||
|
||||
// Reply media routes (v1.9.0)
|
||||
register_rest_route('beep/v1', '/reply/(?P<id>\d+)/media', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [__CLASS__, 'upload_reply_media'],
|
||||
'permission_callback' => ['Beep_Likes', 'can_interact'],
|
||||
]);
|
||||
register_rest_route('beep/v1', '/reply/(?P<id>\d+)/media/(?P<attachment_id>\d+)', [
|
||||
'methods' => 'DELETE',
|
||||
'callback' => [__CLASS__, 'delete_reply_media'],
|
||||
'permission_callback' => ['Beep_Likes', 'can_interact'],
|
||||
]);
|
||||
register_rest_route('beep/v1', '/reply/(?P<id>\d+)/voice', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [__CLASS__, 'upload_reply_voice'],
|
||||
'permission_callback' => ['Beep_Likes', 'can_interact'],
|
||||
]);
|
||||
register_rest_route('beep/v1', '/reply/(?P<id>\d+)/gif', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [__CLASS__, 'set_reply_gif'],
|
||||
'permission_callback' => ['Beep_Likes', 'can_interact'],
|
||||
]);
|
||||
register_rest_route('beep/v1', '/reply/(?P<id>\d+)/quote', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [__CLASS__, 'set_reply_quote'],
|
||||
'permission_callback' => ['Beep_Likes', 'can_interact'],
|
||||
]);
|
||||
register_rest_route('beep/v1', '/reply/(?P<id>\d+)/poll', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [__CLASS__, 'create_reply_poll'],
|
||||
'permission_callback' => ['Beep_Likes', 'can_interact'],
|
||||
]);
|
||||
}
|
||||
|
||||
public static function submit_beep($request) {
|
||||
@@ -88,9 +151,23 @@ class Beep_Replies {
|
||||
return new WP_Error('invalid_post', 'Invalid beep post.', ['status' => 400]);
|
||||
}
|
||||
$content = sanitize_textarea_field($request->get_param('content'));
|
||||
if (empty($content)) {
|
||||
if (mb_strlen($content) > BEEP_CHAR_LIMIT) {
|
||||
return new WP_Error('content_too_long', 'Reply exceeds ' . BEEP_CHAR_LIMIT . ' character limit.', ['status' => 400]);
|
||||
}
|
||||
|
||||
// Allow empty content when media / quote / gif / poll is attached.
|
||||
$has_payload = (
|
||||
$content !== ''
|
||||
|| !empty($request->get_param('quote_id'))
|
||||
|| !empty($request->get_param('gif_url'))
|
||||
|| !empty($request->get_param('has_images'))
|
||||
|| !empty($request->get_param('has_voice'))
|
||||
|| !empty($request->get_param('has_poll'))
|
||||
);
|
||||
if (!$has_payload) {
|
||||
return new WP_Error('empty_content', 'Reply cannot be empty.', ['status' => 400]);
|
||||
}
|
||||
|
||||
$comment_id = wp_insert_comment([
|
||||
'comment_post_ID' => $post_id,
|
||||
'comment_content' => $content,
|
||||
@@ -101,9 +178,164 @@ class Beep_Replies {
|
||||
if (!$comment_id) {
|
||||
return new WP_Error('insert_failed', 'Could not save reply.', ['status' => 500]);
|
||||
}
|
||||
|
||||
// Pre-attach quote + gif URL (uploads happen via the new reply-media routes after insert).
|
||||
if ($request->get_param('quote_id') && is_numeric($request->get_param('quote_id'))) {
|
||||
$quoted = get_post((int) $request->get_param('quote_id'));
|
||||
if ($quoted && $quoted->post_type === 'beep') {
|
||||
update_comment_meta($comment_id, self::META_QUOTE_ID, (int) $quoted->ID);
|
||||
}
|
||||
}
|
||||
if ($gif_url = $request->get_param('gif_url')) {
|
||||
if (filter_var($gif_url, FILTER_VALIDATE_URL)) {
|
||||
update_comment_meta($comment_id, self::META_GIF, esc_url_raw($gif_url));
|
||||
}
|
||||
}
|
||||
|
||||
return rest_ensure_response(['id' => $comment_id]);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* Reply media endpoints (v1.9.0)
|
||||
* Mirror Beep_Media / Beep_Polls but store on comment_meta.
|
||||
* ============================================================ */
|
||||
|
||||
private static function validate_reply_owner($comment_id, $require_auth = true) {
|
||||
$comment = get_comment($comment_id);
|
||||
if (!$comment) return new WP_Error('not_found', 'Reply not found.', ['status' => 404]);
|
||||
if ($require_auth) {
|
||||
$uid = get_current_user_id();
|
||||
if (!$uid || (int) $comment->user_id !== $uid) {
|
||||
if (!current_user_can('moderate_comments')) {
|
||||
return new WP_Error('forbidden', 'Cannot edit this reply.', ['status' => 403]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $comment;
|
||||
}
|
||||
|
||||
public static function upload_reply_media($request) {
|
||||
$comment_id = (int) $request['id'];
|
||||
$check = self::validate_reply_owner($comment_id);
|
||||
if (is_wp_error($check)) return $check;
|
||||
if (empty($_FILES['file'])) {
|
||||
return new WP_Error('no_file', 'No file uploaded.');
|
||||
}
|
||||
$result = Beep_Media::handle_upload_for_parent($comment_id, $_FILES['file'], 'comment');
|
||||
if (is_wp_error($result)) return $result;
|
||||
return rest_ensure_response($result);
|
||||
}
|
||||
|
||||
public static function delete_reply_media($request) {
|
||||
$comment_id = (int) $request['id'];
|
||||
$attachment_id = (int) $request['attachment_id'];
|
||||
$check = self::validate_reply_owner($comment_id);
|
||||
if (is_wp_error($check)) return $check;
|
||||
Beep_Media::remove_image_from_parent($comment_id, $attachment_id, 'comment');
|
||||
return rest_ensure_response(['ok' => true]);
|
||||
}
|
||||
|
||||
public static function upload_reply_voice($request) {
|
||||
$comment_id = (int) $request['id'];
|
||||
$check = self::validate_reply_owner($comment_id);
|
||||
if (is_wp_error($check)) return $check;
|
||||
if (empty($_FILES['file'])) {
|
||||
return new WP_Error('no_file', 'No file uploaded.');
|
||||
}
|
||||
$result = Beep_Media::handle_voice_upload_for_parent($comment_id, $_FILES['file'], 'comment');
|
||||
if (is_wp_error($result)) return $result;
|
||||
return rest_ensure_response($result);
|
||||
}
|
||||
|
||||
public static function set_reply_gif($request) {
|
||||
$comment_id = (int) $request['id'];
|
||||
$check = self::validate_reply_owner($comment_id);
|
||||
if (is_wp_error($check)) return $check;
|
||||
$body = $request->get_json_params();
|
||||
$url = isset($body['gif_url']) ? esc_url_raw($body['gif_url']) : '';
|
||||
if (!filter_var($url, FILTER_VALIDATE_URL)) {
|
||||
return new WP_Error('invalid_url', 'Invalid GIF URL.');
|
||||
}
|
||||
update_comment_meta($comment_id, self::META_GIF, $url);
|
||||
return rest_ensure_response(['gif_url' => $url]);
|
||||
}
|
||||
|
||||
public static function set_reply_quote($request) {
|
||||
$comment_id = (int) $request['id'];
|
||||
$check = self::validate_reply_owner($comment_id);
|
||||
if (is_wp_error($check)) return $check;
|
||||
$body = $request->get_json_params();
|
||||
$quote_id = isset($body['quote_id']) ? (int) $body['quote_id'] : 0;
|
||||
$quoted = $quote_id ? get_post($quote_id) : null;
|
||||
if (!$quoted || $quoted->post_type !== 'beep') {
|
||||
return new WP_Error('invalid_quote', 'Cannot quote that beep.');
|
||||
}
|
||||
update_comment_meta($comment_id, self::META_QUOTE_ID, $quote_id);
|
||||
return rest_ensure_response(['quote_id' => $quote_id]);
|
||||
}
|
||||
|
||||
public static function create_reply_poll($request) {
|
||||
$comment_id = (int) $request['id'];
|
||||
$check = self::validate_reply_owner($comment_id);
|
||||
if (is_wp_error($check)) return $check;
|
||||
$body = $request->get_json_params();
|
||||
$question = sanitize_text_field($body['question'] ?? '');
|
||||
$options = isset($body['options']) ? (array) $body['options'] : [];
|
||||
if (empty($question) || count($options) < 2 || count($options) > 6) {
|
||||
return new WP_Error('invalid_poll', 'Poll needs a question and 2-6 options.', ['status' => 400]);
|
||||
}
|
||||
$options = array_map('sanitize_text_field', $options);
|
||||
global $wpdb;
|
||||
$table = $wpdb->prefix . 'beep_reply_polls';
|
||||
$votes = array_fill(0, count($options), 0);
|
||||
$wpdb->insert($table, [
|
||||
'comment_id' => $comment_id,
|
||||
'question' => $question,
|
||||
'options' => json_encode($options),
|
||||
'votes' => json_encode($votes),
|
||||
'total_votes' => 0,
|
||||
'created_at' => current_time('mysql'),
|
||||
], ['%d', '%s', '%s', '%s', '%d', '%s']);
|
||||
if ($wpdb->last_error) {
|
||||
return new WP_Error('db_error', 'Could not save poll: ' . $wpdb->last_error, ['status' => 500]);
|
||||
}
|
||||
$poll_id = $wpdb->insert_id;
|
||||
update_comment_meta($comment_id, 'beep_reply_poll_id', $poll_id);
|
||||
return rest_ensure_response([
|
||||
'id' => $poll_id,
|
||||
'question' => $question,
|
||||
'options' => $options,
|
||||
'votes' => $votes,
|
||||
]);
|
||||
}
|
||||
|
||||
/* Reply media getters (used by renderer). */
|
||||
public static function get_reply_images($comment_id) {
|
||||
$ids = get_comment_meta($comment_id, self::META_IMAGES, false);
|
||||
return array_filter(array_map('intval', (array) $ids));
|
||||
}
|
||||
public static function get_reply_voice_url($comment_id) {
|
||||
return esc_url_raw(get_comment_meta($comment_id, self::META_VOICE, true));
|
||||
}
|
||||
public static function get_reply_gif_url($comment_id) {
|
||||
return esc_url_raw(get_comment_meta($comment_id, self::META_GIF, true));
|
||||
}
|
||||
public static function get_reply_quoted_post_id($comment_id) {
|
||||
return (int) get_comment_meta($comment_id, self::META_QUOTE_ID, true);
|
||||
}
|
||||
public static function get_reply_poll($comment_id) {
|
||||
global $wpdb;
|
||||
$table = $wpdb->prefix . 'beep_reply_polls';
|
||||
$row = $wpdb->get_row($wpdb->prepare(
|
||||
"SELECT * FROM $table WHERE comment_id = %d ORDER BY id DESC LIMIT 1",
|
||||
$comment_id
|
||||
), ARRAY_A);
|
||||
if (!$row) return null;
|
||||
$row['options'] = json_decode($row['options'], true) ?: [];
|
||||
$row['votes'] = json_decode($row['votes'], true) ?: [];
|
||||
return $row;
|
||||
}
|
||||
|
||||
public static function delete_beep($request) {
|
||||
$post_id = (int) $request['id'];
|
||||
$post = get_post($post_id);
|
||||
|
||||
+191
-62
@@ -146,62 +146,11 @@ function beep_render_composer() {
|
||||
placeholder="What's happening?"
|
||||
maxlength="<?php echo (int) BEEP_CHAR_LIMIT; ?>"
|
||||
rows="1"></textarea>
|
||||
<div class="beep-compose-media-preview" data-beep-media-preview hidden></div>
|
||||
<div class="beep-compose-voice-preview" data-beep-voice-preview hidden>
|
||||
<div class="beep-voice-preview-card">
|
||||
<svg viewBox="0 0 24 24" width="20" height="20" 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>
|
||||
<span>Voice note</span>
|
||||
<button class="beep-voice-remove" data-beep-voice-remove type="button" title="Remove">×</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="beep-compose-gif" data-beep-gif-preview hidden></div>
|
||||
<div class="beep-compose-quote" data-beep-compose-quote hidden>
|
||||
<div class="beep-quote-input-row">
|
||||
<input type="text" class="beep-quote-lookup" placeholder="Paste beep URL or ID..." data-beep-quote-lookup>
|
||||
<button type="button" class="beep-quote-lookup-btn" data-beep-quote-lookup-btn>Add</button>
|
||||
</div>
|
||||
<div class="beep-quote-preview" data-beep-quote-preview hidden></div>
|
||||
<button type="button" class="beep-quote-remove" data-beep-quote-remove hidden>Remove quote</button>
|
||||
</div>
|
||||
<div class="beep-compose-poll" data-beep-compose-poll hidden>
|
||||
<div class="beep-poll-input-row">
|
||||
<input type="text" class="beep-poll-question" data-beep-poll-question placeholder="Ask a question...">
|
||||
</div>
|
||||
<div class="beep-poll-options" data-beep-poll-options>
|
||||
<div class="beep-poll-option-row">
|
||||
<input type="text" class="beep-poll-option" data-beep-poll-option placeholder="Option 1" maxlength="100">
|
||||
</div>
|
||||
<div class="beep-poll-option-row">
|
||||
<input type="text" class="beep-poll-option" data-beep-poll-option placeholder="Option 2" maxlength="100">
|
||||
</div>
|
||||
</div>
|
||||
<div class="beep-poll-actions">
|
||||
<button type="button" class="beep-poll-add-option" data-beep-poll-add-option>+ Add option</button>
|
||||
<button type="button" class="beep-poll-remove-option" data-beep-poll-remove-option hidden>− Remove</button>
|
||||
<button type="button" class="beep-poll-remove" data-beep-poll-remove>Remove poll</button>
|
||||
</div>
|
||||
</div>
|
||||
<?php echo beep_render_media_previews('compose'); ?>
|
||||
<?php echo beep_render_quote_section('compose'); ?>
|
||||
<?php echo beep_render_poll_section('compose'); ?>
|
||||
<div class="beep-compose-footer">
|
||||
<div class="beep-compose-tools">
|
||||
<label class="beep-compose-tool beep-media-btn" title="Add up to 4 images">
|
||||
<input type="file" accept="image/jpeg,image/png,image/gif,image/webp" multiple data-beep-media-input hidden>
|
||||
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M3 5.5C3 4.119 4.119 3 5.5 3h13C19.881 3 21 4.119 21 5.5v13c0 1.381-1.119 2.5-2.5 2.5h-13C4.119 21 3 19.881 3 18.5v-13zM5.5 5c-.276 0-.5.224-.5.5v9.086l3-3 3 3 5-5 3 3V5.5c0-.276-.224-.5-.5-.5h-13zM19 15.414l-3-3-5 5-3-3-3 3V18.5c0 .276.224.5.5.5h13c.276 0 .5-.224.5-.5v-3.086zM9.75 7C8.784 7 8 7.784 8 8.75s.784 1.75 1.75 1.75 1.75-.784 1.75-1.75S10.716 7 9.75 7z"/></svg>
|
||||
</label>
|
||||
<span class="beep-media-count" data-beep-media-count hidden>0/4</span>
|
||||
<button type="button" class="beep-compose-tool beep-gif-btn" data-beep-gif-btn title="Add GIF">
|
||||
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M3 5.5C3 4.119 4.119 3 5.5 3h13C19.881 3 21 4.119 21 5.5v13c0 1.381-1.119 2.5-2.5 2.5h-13C4.119 21 3 19.881 3 18.5v-13zM5.5 5c-.276 0-.5.224-.5.5v13c0 .276.224.5.5.5h13c.276 0 .5-.224.5-.5v-13c0-.276-.224-.5-.5-.5h-13zM18 10.711V9.25h-3.74v5.5h1.44v-1.719h1.7V11.57h-1.7v-.859H18zM11.79 9.25h1.44v5.5h-1.44v-5.5zm-3.07 1.375c.34 0 .77.172 1.02.43l1.03-.86c-.51-.601-1.28-.945-2.05-.945C7.19 9.25 6 10.453 6 12s1.19 2.75 2.72 2.75c.85 0 1.54-.344 2.05-.945v-2.149H8.38v1.032H9.4v.515c-.17.086-.42.172-.68.172-.76 0-1.36-.602-1.36-1.375 0-.688.6-1.375 1.36-1.375z"/></svg>
|
||||
</button>
|
||||
<label class="beep-compose-tool beep-voice-btn" title="Add voice note">
|
||||
<input type="file" accept="audio/mpeg,audio/ogg,audio/wav,audio/webm,audio/mp4,audio/x-m4a" data-beep-voice-input hidden>
|
||||
<svg viewBox="0 0 24 24" width="20" height="20" 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>
|
||||
</label>
|
||||
<button type="button" class="beep-compose-tool beep-poll-btn" data-beep-poll-btn title="Add poll">
|
||||
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M6 5c-1.1 0-2 .895-2 2s.9 2 2 2 2-.895 2-2-.9-2-2-2zM2 7c0-2.209 1.79-4 4-4s4 1.791 4 4-1.79 4-4 4-4-1.791-4-4zm20 1H12V6h10v2zM6 15c-1.1 0-2 .895-2 2s.9 2 2 2 2-.895 2-2-.9-2-2-2zm-4 2c0-2.209 1.79-4 4-4s4 1.791 4 4-1.79 4-4 4-4-1.791-4-4zm20 1H12v-2h10v2z"/></svg>
|
||||
</button>
|
||||
<button type="button" class="beep-compose-tool beep-quote-composer-btn" data-beep-quote-composer-btn title="Quote a beep">
|
||||
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M14.23 2.854c.98-.977 2.56-.977 3.54 0l3.38 3.378c.97.977.97 2.559 0 3.536L9.91 21H3v-6.914L14.23 2.854zm2.12 1.414c-.19-.195-.51-.195-.7 0L5 14.914V19h4.09L19.73 8.354c.2-.196.2-.512 0-.708l-3.38-3.378z"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
<?php echo beep_render_compose_toolbar('compose'); ?>
|
||||
<div class="beep-compose-submit-row">
|
||||
<span class="beep-char-counter" data-beep-counter><?php echo (int) BEEP_CHAR_LIMIT; ?></span>
|
||||
<button class="beep-button beep-post-button" data-beep-submit disabled>Beep</button>
|
||||
@@ -213,6 +162,136 @@ function beep_render_composer() {
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the media previews block (image grid + voice card + GIF card).
|
||||
* Shared between compose and reply forms.
|
||||
*
|
||||
* @param string $scope 'compose' or 'reply'
|
||||
*/
|
||||
function beep_render_media_previews($scope = 'compose') {
|
||||
$cls = $scope === 'reply' ? 'beep-reply' : 'beep-compose';
|
||||
?>
|
||||
<div class="<?php echo esc_attr($cls); ?>-media-preview" data-beep-media-preview hidden></div>
|
||||
<div class="<?php echo esc_attr($cls); ?>-voice-preview" data-beep-voice-preview hidden>
|
||||
<div class="beep-voice-preview-card">
|
||||
<svg viewBox="0 0 24 24" width="20" height="20" 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>
|
||||
<span>Voice note</span>
|
||||
<button class="beep-voice-remove" data-beep-voice-remove type="button" title="Remove">×</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="<?php echo esc_attr($cls); ?>-gif" data-beep-gif-preview hidden></div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the quote-lookup section (input + preview).
|
||||
* Shared between compose and reply forms.
|
||||
*/
|
||||
function beep_render_quote_section($scope = 'compose') {
|
||||
$cls = $scope === 'reply' ? 'beep-reply' : 'beep-compose';
|
||||
?>
|
||||
<div class="<?php echo esc_attr($cls); ?>-quote" data-beep-compose-quote hidden>
|
||||
<div class="beep-quote-input-row">
|
||||
<input type="text" class="beep-quote-lookup" placeholder="Paste beep URL or ID..." data-beep-quote-lookup>
|
||||
<button type="button" class="beep-quote-lookup-btn" data-beep-quote-lookup-btn>Add</button>
|
||||
</div>
|
||||
<div class="beep-quote-preview" data-beep-quote-preview hidden></div>
|
||||
<button type="button" class="beep-quote-remove" data-beep-quote-remove hidden>Remove quote</button>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the poll-creation section.
|
||||
* Shared between compose and reply forms.
|
||||
*/
|
||||
function beep_render_poll_section($scope = 'compose') {
|
||||
$cls = $scope === 'reply' ? 'beep-reply' : 'beep-compose';
|
||||
?>
|
||||
<div class="<?php echo esc_attr($cls); ?>-poll" data-beep-compose-poll hidden>
|
||||
<div class="beep-poll-input-row">
|
||||
<input type="text" class="beep-poll-question" data-beep-poll-question placeholder="Ask a question...">
|
||||
</div>
|
||||
<div class="beep-poll-options" data-beep-poll-options>
|
||||
<div class="beep-poll-option-row">
|
||||
<input type="text" class="beep-poll-option" data-beep-poll-option placeholder="Option 1" maxlength="100">
|
||||
</div>
|
||||
<div class="beep-poll-option-row">
|
||||
<input type="text" class="beep-poll-option" data-beep-poll-option placeholder="Option 2" maxlength="100">
|
||||
</div>
|
||||
</div>
|
||||
<div class="beep-poll-actions">
|
||||
<button type="button" class="beep-poll-add-option" data-beep-poll-add-option>+ Add option</button>
|
||||
<button type="button" class="beep-poll-remove-option" data-beep-poll-remove-option hidden>− Remove</button>
|
||||
<button type="button" class="beep-poll-remove" data-beep-poll-remove>Remove poll</button>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the compose toolbar (image / GIF / voice / poll / quote buttons).
|
||||
* Shared between compose and reply forms.
|
||||
*/
|
||||
function beep_render_compose_toolbar($scope = 'compose') {
|
||||
$cls = $scope === 'reply' ? 'beep-reply' : 'beep-compose';
|
||||
?>
|
||||
<div class="<?php echo esc_attr($cls); ?>-tools">
|
||||
<label class="<?php echo esc_attr($cls); ?>-tool <?php echo esc_attr($cls); ?>-media-btn" title="Add up to 4 images">
|
||||
<input type="file" accept="image/jpeg,image/png,image/gif,image/webp" multiple data-beep-media-input hidden>
|
||||
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M3 5.5C3 4.119 4.119 3 5.5 3h13C19.881 3 21 4.119 21 5.5v13c0 1.381-1.119 2.5-2.5 2.5h-13C4.119 21 3 19.881 3 18.5v-13zM5.5 5c-.276 0-.5.224-.5.5v9.086l3-3 3 3 5-5 3 3V5.5c0-.276-.224-.5-.5-.5h-13zM19 15.414l-3-3-5 5-3-3-3 3V18.5c0 .276.224.5.5.5h13c.276 0 .5-.224.5-.5v-3.086zM9.75 7C8.784 7 8 7.784 8 8.75s.784 1.75 1.75 1.75 1.75-.784 1.75-1.75S10.716 7 9.75 7z"/></svg>
|
||||
</label>
|
||||
<span class="beep-media-count" data-beep-media-count hidden>0/4</span>
|
||||
<button type="button" class="<?php echo esc_attr($cls); ?>-tool <?php echo esc_attr($cls); ?>-gif-btn" data-beep-gif-btn title="Add GIF">
|
||||
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M3 5.5C3 4.119 4.119 3 5.5 3h13C19.881 3 21 4.119 21 5.5v13c0 1.381-1.119 2.5-2.5 2.5h-13C4.119 21 3 19.881 3 18.5v-13zM5.5 5c-.276 0-.5.224-.5.5v13c0 .276.224.5.5.5h13c.276 0 .5-.224.5-.5v-13c0-.276-.224-.5-.5-.5h-13zM18 10.711V9.25h-3.74v5.5h1.44v-1.719h1.7V11.57h-1.7v-.859H18zM11.79 9.25h1.44v5.5h-1.44v-5.5zm-3.07 1.375c.34 0 .77.172 1.02.43l1.03-.86c-.51-.601-1.28-.945-2.05-.945C7.19 9.25 6 10.453 6 12s1.19 2.75 2.72 2.75c.85 0 1.54-.344 2.05-.945v-2.149H8.38v1.032H9.4v.515c-.17.086-.42.172-.68.172-.76 0-1.36-.602-1.36-1.375 0-.688.6-1.375 1.36-1.375z"/></svg>
|
||||
</button>
|
||||
<label class="<?php echo esc_attr($cls); ?>-tool <?php echo esc_attr($cls); ?>-voice-btn" title="Add voice note">
|
||||
<input type="file" accept="audio/mpeg,audio/ogg,audio/wav,audio/webm,audio/mp4,audio/x-m4a" data-beep-voice-input hidden>
|
||||
<svg viewBox="0 0 24 24" width="20" height="20" 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>
|
||||
</label>
|
||||
<button type="button" class="<?php echo esc_attr($cls); ?>-tool <?php echo esc_attr($cls); ?>-poll-btn" data-beep-poll-btn title="Add poll">
|
||||
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M6 5c-1.1 0-2 .895-2 2s.9 2 2 2 2-.895 2-2-.9-2-2-2zM2 7c0-2.209 1.79-4 4-4s4 1.791 4 4-1.79 4-4 4-4-1.791-4-4zm20 1H12V6h10v2zM6 15c-1.1 0-2 .895-2 2s.9 2 2 2 2-.895 2-2-.9-2-2-2zm-4 2c0-2.209 1.79-4 4-4s4 1.791 4 4-1.79 4-4 4-4-1.791-4-4zm20 1H12v-2h10v2z"/></svg>
|
||||
</button>
|
||||
<button type="button" class="<?php echo esc_attr($cls); ?>-tool <?php echo esc_attr($cls); ?>-quote-composer-btn" data-beep-quote-composer-btn title="Quote a beep">
|
||||
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M14.23 2.854c.98-.977 2.56-.977 3.54 0l3.38 3.378c.97.977.97 2.559 0 3.536L9.91 21H3v-6.914L14.23 2.854zm2.12 1.414c-.19-.195-.51-.195-.7 0L5 14.914V19h4.09L19.73 8.354c.2-.196.2-.512 0-.708l-3.38-3.378z"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a reply poll (v1.9.0).
|
||||
* Mirrors beep_render_poll() for posts.
|
||||
*/
|
||||
function beep_render_reply_poll($comment_id) {
|
||||
$poll = Beep_Replies::get_reply_poll($comment_id);
|
||||
if (!$poll) return '';
|
||||
$total = (int) $poll['total_votes'];
|
||||
$opts = $poll['options'];
|
||||
$votes = $poll['votes'];
|
||||
$has_ended = $poll['ends_at'] && strtotime($poll['ends_at']) < time();
|
||||
?>
|
||||
<div class="beep-poll-container" data-beep-reply-poll data-beep-reply-poll-id="<?php echo (int) $poll['id']; ?>">
|
||||
<div class="beep-poll-question"><?php echo esc_html($poll['question']); ?></div>
|
||||
<div class="beep-poll-options">
|
||||
<?php foreach ($opts as $i => $opt) :
|
||||
$pct = $total > 0 ? round(((int) ($votes[$i] ?? 0)) * 100 / $total) : 0;
|
||||
$has_voted = false; // kept simple for v1.9.0; full voting comes in v1.9.1
|
||||
?>
|
||||
<div class="beep-poll-result<?php echo $has_voted ? ' is-voted' : ''; ?>">
|
||||
<div class="beep-poll-result-bar" style="width:<?php echo (int) $pct; ?>%"></div>
|
||||
<span class="beep-poll-result-text"><?php echo esc_html($opt); ?></span>
|
||||
<span class="beep-poll-result-percent"><?php echo (int) $pct; ?>%</span>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<div class="beep-poll-meta">
|
||||
<span class="beep-poll-votes"><?php echo $total; ?> <?php echo $total === 1 ? 'vote' : 'votes'; ?></span>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a single beep post.
|
||||
*
|
||||
@@ -429,10 +508,13 @@ function beep_render_poll($post_id) {
|
||||
* @param int $parent The parent comment ID (0 for top-level reply).
|
||||
*/
|
||||
function beep_render_reply_form($post_id, $parent) {
|
||||
if ( ! is_user_logged_in() || beep_user_is_banned( get_current_user_id() ) ) {
|
||||
return '';
|
||||
}
|
||||
$hidden = $parent ? 'hidden' : '';
|
||||
ob_start();
|
||||
?>
|
||||
<div class="beep-reply-form" data-beep-reply-form data-beep-parent="<?php echo (int) $parent; ?>" <?php echo $hidden; ?>>
|
||||
<div class="beep-reply-form beep-reply-form-compact" data-beep-reply-form data-beep-parent="<?php echo (int) $parent; ?>" <?php echo $hidden; ?>>
|
||||
<?php echo beep_avatar_img(get_current_user_id(), 'beep-avatar-sm'); ?>
|
||||
<div class="beep-reply-form-body">
|
||||
<textarea
|
||||
@@ -440,13 +522,19 @@ function beep_render_reply_form($post_id, $parent) {
|
||||
placeholder="Post your reply"
|
||||
maxlength="<?php echo (int) BEEP_CHAR_LIMIT; ?>"
|
||||
rows="1"></textarea>
|
||||
<?php echo beep_render_media_previews('reply'); ?>
|
||||
<?php echo beep_render_quote_section('reply'); ?>
|
||||
<?php echo beep_render_poll_section('reply'); ?>
|
||||
<div class="beep-reply-footer">
|
||||
<span class="beep-char-counter"><?php echo (int) BEEP_CHAR_LIMIT; ?></span>
|
||||
<button class="beep-button beep-reply-submit"
|
||||
data-beep-reply-submit="<?php echo (int) $post_id; ?>"
|
||||
data-beep-reply-parent="<?php echo (int) $parent; ?>"
|
||||
type="button"
|
||||
disabled>Reply</button>
|
||||
<?php echo beep_render_compose_toolbar('reply'); ?>
|
||||
<div class="beep-reply-submit-row">
|
||||
<span class="beep-char-counter"><?php echo (int) BEEP_CHAR_LIMIT; ?></span>
|
||||
<button class="beep-button beep-reply-submit"
|
||||
data-beep-reply-submit="<?php echo (int) $post_id; ?>"
|
||||
data-beep-reply-parent="<?php echo (int) $parent; ?>"
|
||||
type="button"
|
||||
disabled>Reply</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -492,6 +580,13 @@ function beep_render_reply($comment, $all_comments = [], $depth = 0, $post_id =
|
||||
$like_count = Beep_Likes::get_count($comment->comment_ID, 'reply');
|
||||
$user_liked = Beep_Likes::user_has_liked($comment->comment_ID, 'reply');
|
||||
|
||||
// Reply media (v1.9.0)
|
||||
$reply_images = Beep_Replies::get_reply_images($comment->comment_ID);
|
||||
$reply_voice = Beep_Replies::get_reply_voice_url($comment->comment_ID);
|
||||
$reply_gif = Beep_Replies::get_reply_gif_url($comment->comment_ID);
|
||||
$reply_quote_id = Beep_Replies::get_reply_quoted_post_id($comment->comment_ID);
|
||||
$reply_quote = $reply_quote_id ? get_post($reply_quote_id) : null;
|
||||
|
||||
$depth_class = 'beep-depth-' . min((int) $depth, 2);
|
||||
|
||||
ob_start();
|
||||
@@ -517,6 +612,40 @@ function beep_render_reply($comment, $all_comments = [], $depth = 0, $post_id =
|
||||
</div>
|
||||
<div class="beep-text"><?php echo $content; ?></div>
|
||||
|
||||
<?php if (!empty($reply_images)) : ?>
|
||||
<div class="beep-media-attachments">
|
||||
<?php echo Beep_Media::render_image_gallery($reply_images); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if ($reply_voice) : ?>
|
||||
<div class="beep-media-attachments">
|
||||
<div class="beep-voice-note">
|
||||
<audio controls preload="none">
|
||||
<source src="<?php echo esc_url($reply_voice); ?>">
|
||||
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>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if ($reply_quote && $reply_quote->post_type === 'beep') : ?>
|
||||
<div class="beep-quoted-container">
|
||||
<?php
|
||||
$quoted_data = Beep_Quotes::get_quoted_post($reply_quote->ID);
|
||||
if ($quoted_data) echo Beep_Quotes::render_quoted_post($reply_quote->ID);
|
||||
?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if ($reply_gif) : ?>
|
||||
<div class="beep-gif-embed">
|
||||
<img src="<?php echo esc_url($reply_gif); ?>" alt="GIF" loading="lazy">
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php echo beep_render_reply_poll($comment->comment_ID); ?>
|
||||
|
||||
<?php if ($can_interact) : ?>
|
||||
<div class="beep-actions beep-actions-reply">
|
||||
<button class="beep-action beep-action-reply"
|
||||
|
||||
Reference in New Issue
Block a user