Files
beep/includes/class-shortcode.php
T

288 lines
11 KiB
PHP
Executable File

<?php
if (!defined('ABSPATH')) {
exit;
}
class Beep_Shortcode {
public static function init() {
add_shortcode('beep_feed', [__CLASS__, 'render_feed']);
add_action('rest_api_init', [__CLASS__, 'register_routes']);
add_action('rest_api_init', [__CLASS__, 'register_media_routes']);
add_action('rest_api_init', [__CLASS__, 'register_thread_routes']);
add_action('rest_api_init', [__CLASS__, 'register_quote_routes']);
}
public static function register_routes() {
register_rest_route('beep/v1', '/feed', [
'methods' => 'GET',
'callback' => [__CLASS__, 'fetch_feed'],
'permission_callback' => '__return_true',
'args' => [
'sort' => ['default' => 'latest'],
'limit' => ['default' => 50, 'validate_callback' => function ($v) { return is_numeric($v); }],
],
]);
}
/**
* REST endpoint: re-render the feed list with the given sort.
*/
public static function fetch_feed($request) {
$sort = $request->get_param('sort') === 'top' ? 'top' : 'latest';
$limit = max(1, (int) $request->get_param('limit'));
$posts = ($sort === 'top') ? self::get_top_beeps($limit) : self::get_latest_beeps($limit);
$html = '';
foreach ($posts as $p) {
$html .= beep_render_post($p);
}
if ($html === '') {
$html = '<div class="beep-empty">No beeps yet.</div>';
}
return rest_ensure_response(['html' => $html, 'sort' => $sort]);
}
public static function get_latest_beeps($limit) {
global $wpdb;
return $wpdb->get_results($wpdb->prepare(
"SELECT * FROM {$wpdb->posts}
WHERE post_type = %s AND post_status = 'publish'
ORDER BY post_date DESC
LIMIT %d",
Beep_CPT::POST_TYPE, $limit
));
}
public static function get_top_beeps($limit) {
global $wpdb;
$likes = Beep_Likes::table_name();
return $wpdb->get_results($wpdb->prepare(
"SELECT p.*, COALESCE(l.cnt, 0) AS like_count
FROM {$wpdb->posts} p
LEFT JOIN (
SELECT post_id, COUNT(*) AS cnt
FROM $likes
WHERE object_type = 'post'
GROUP BY post_id
) l ON l.post_id = p.ID
WHERE p.post_type = %s AND p.post_status = 'publish'
ORDER BY COALESCE(l.cnt, 0) DESC, p.post_date DESC
LIMIT %d",
Beep_CPT::POST_TYPE, $limit
));
}
public static function render_feed($atts) {
$atts = shortcode_atts([
'limit' => 50,
'header' => 'Beep!',
'show_wordmark' => true,
'sort' => 'latest',
], $atts, 'beep_feed');
$limit = max(1, (int) $atts['limit']);
$sort = ($atts['sort'] === 'top') ? 'top' : 'latest';
$posts = ($sort === 'top') ? self::get_top_beeps($limit) : self::get_latest_beeps($limit);
$has_posts = !empty($posts);
ob_start();
?>
<div class="beep-feed" data-beep-feed>
<div class="beep-embed">
<div class="beep-feed-header">
<?php if (!empty($atts['show_wordmark'])): ?>
<?php echo beep_logo_svg(); ?>
<?php else: ?>
<span class="beep-feed-header-text"><?php echo esc_html($atts['header']); ?></span>
<?php endif; ?>
</div>
<?php if ($has_posts) : ?>
<div class="beep-tabs" data-beep-tabs role="tablist">
<button type="button" class="beep-tab <?php echo $sort === 'latest' ? 'is-active' : ''; ?>"
data-beep-sort="latest" role="tab">Latest</button>
<button type="button" class="beep-tab <?php echo $sort === 'top' ? 'is-active' : ''; ?>"
data-beep-sort="top" role="tab">Top</button>
</div>
<?php endif; ?>
<?php if (is_user_logged_in() && !beep_user_is_banned(get_current_user_id())) : ?>
<?php echo beep_render_composer(); ?>
<?php elseif (is_user_logged_in()) : ?>
<div class="beep-notice beep-notice-banned">
Your account has been suspended from beeping.
</div>
<?php else : ?>
<div class="beep-notice beep-notice-login">
<p><strong>Join in.</strong> Sign in to like and reply.</p>
<a class="beep-button" href="<?php echo esc_url(wp_login_url(get_permalink())); ?>">Log in</a>
<?php if (get_option('users_can_register')) : ?>
<a class="beep-button beep-button-secondary" href="<?php echo esc_url(wp_registration_url()); ?>">Sign up</a>
<?php endif; ?>
</div>
<?php endif; ?>
<div class="beep-list" data-beep-list>
<?php
if ($has_posts) {
foreach ($posts as $p) {
echo beep_render_post($p);
}
} else {
echo '<div class="beep-empty">No beeps yet.</div>';
}
?>
</div><!-- .beep-list -->
</div><!-- .beep-embed -->
</div><!-- .beep-feed -->
<?php
return ob_get_clean() . beep_render_thread_modal() . beep_render_gif_modal();
}
/**
* Register additional REST routes for media upload.
*/
public static function register_media_routes() {
register_rest_route('beep/v1', '/media/(?P<post_id>\d+)', [
'methods' => 'POST',
'callback' => [__CLASS__, 'upload_media'],
'permission_callback' => function () {
return is_user_logged_in() && !beep_user_is_banned(get_current_user_id());
},
]);
register_rest_route('beep/v1', '/media/(?P<post_id>\d+)/(?P<attachment_id>\d+)', [
'methods' => 'DELETE',
'callback' => [__CLASS__, 'delete_media'],
'permission_callback' => function () {
return is_user_logged_in() && !beep_user_is_banned(get_current_user_id());
},
]);
register_rest_route('beep/v1', '/voice/(?P<post_id>\d+)', [
'methods' => 'POST',
'callback' => [__CLASS__, 'upload_voice'],
'permission_callback' => function () {
return is_user_logged_in() && !beep_user_is_banned(get_current_user_id());
},
]);
}
public static function upload_media($request) {
$post_id = (int) $request->get_param('post_id');
$post = get_post($post_id);
if (!$post || $post->post_type !== Beep_CPT::POST_TYPE) {
return new WP_Error('not_found', 'Beep not found.', 404);
}
if (!current_user_can('edit_post', $post_id)) {
return new WP_Error('forbidden', 'Cannot edit this beep.', 403);
}
if (empty($_FILES['file'])) {
return new WP_Error('no_file', 'No file uploaded.');
}
$result = Beep_Media::handle_upload($post_id, $_FILES['file']);
if (is_wp_error($result)) {
return $result;
}
return rest_ensure_response($result);
}
public static function delete_media($request) {
$post_id = (int) $request->get_param('post_id');
$attachment_id = (int) $request->get_param('attachment_id');
$post = get_post($post_id);
if (!$post || $post->post_type !== Beep_CPT::POST_TYPE) {
return new WP_Error('not_found', 'Beep not found.', 404);
}
if (!current_user_can('edit_post', $post_id)) {
return new WP_Error('forbidden', 'Cannot edit this beep.', 403);
}
Beep_Media::remove_image($post_id, $attachment_id);
return rest_ensure_response(['ok' => true]);
}
public static function upload_voice($request) {
$post_id = (int) $request->get_param('post_id');
$post = get_post($post_id);
if (!$post || $post->post_type !== Beep_CPT::POST_TYPE) {
return new WP_Error('not_found', 'Beep not found.', 404);
}
if (!current_user_can('edit_post', $post_id)) {
return new WP_Error('forbidden', 'Cannot edit this beep.', 403);
}
if (empty($_FILES['file'])) {
return new WP_Error('no_file', 'No file uploaded.');
}
$result = Beep_Media::handle_voice_upload($post_id, $_FILES['file']);
if (is_wp_error($result)) {
return $result;
}
return rest_ensure_response($result);
}
public static function register_thread_routes() {
register_rest_route('beep/v1', '/thread/(?P<post_id>\d+)', [
'methods' => 'GET',
'callback' => [__CLASS__, 'get_thread'],
'permission_callback' => '__return_true',
]);
}
public static function get_thread($request) {
$post_id = (int) $request->get_param('post_id');
if (get_post_type($post_id) !== Beep_CPT::POST_TYPE) {
return new WP_Error('not_found', 'Beep not found.', 404);
}
$depth = (int) $request->get_param('depth') ?: 10;
$thread = Beep_Replies::get_thread($post_id, 0, $depth);
return rest_ensure_response($thread);
}
public static function register_quote_routes() {
register_rest_route('beep/v1', '/quote/(?P<post_id>\d+)', [
'methods' => 'GET',
'callback' => [__CLASS__, 'get_quoted_post'],
'permission_callback' => '__return_true',
]);
register_rest_route('beep/v1', '/lookup', [
'methods' => 'GET',
'callback' => [__CLASS__, 'lookup_beep'],
'permission_callback' => '__return_true',
]);
}
public static function get_quoted_post($request) {
$post_id = (int) $request->get_param('post_id');
$quoted = Beep_Quotes::get_quoted_post($post_id);
if (!$quoted) {
return new WP_Error('not_found', 'No quoted post found.', 404);
}
return rest_ensure_response($quoted);
}
public static function lookup_beep($request) {
$query = sanitize_text_field($request->get_param('q'));
if (!$query) {
return new WP_Error('missing_query', 'Query parameter q is required.', 400);
}
if (is_numeric($query)) {
$post = get_post((int) $query);
if ($post && $post->post_type === Beep_CPT::POST_TYPE) {
return rest_ensure_response(Beep_Quotes::get_quoted_post($post->ID));
}
}
if (preg_match('/\[beep:(\d+)\]/', $query, $m)) {
$post = get_post((int) $m[1]);
if ($post && $post->post_type === Beep_CPT::POST_TYPE) {
return rest_ensure_response(Beep_Quotes::get_quoted_post($post->ID));
}
}
return new WP_Error('not_found', 'Beep not found.', 404);
}
}