__( 'Messages', 'frontglut' ), 'icon' => 'format-chat', 'group' => 'comms', 'order' => 61, 'render' => array( $this, 'screen' ), 'badge' => array( $this, 'unread' ) ); return $i; } ); Ajax::register( 'message_send', array( $this, 'ajax_send' ) ); } public function unread() { global $wpdb; return (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->prefix}frontglut_messages WHERE to_id = %d AND is_read = 0", get_current_user_id() ) ); // phpcs:ignore WordPress.DB.PreparedSQL } /** Users the current user may start a conversation with. */ private function recipients() { $uid = get_current_user_id(); $out = array(); if ( \FrontGlut\Core\Caps::can( 'frontglut_manage_settings' ) ) { foreach ( get_users( array( 'number' => 300, 'exclude' => array( $uid ), 'fields' => array( 'ID', 'display_name' ), 'orderby' => 'display_name' ) ) as $u ) { $out[ $u->ID ] = $u->display_name; } return $out; } foreach ( get_users( array( 'capability' => 'frontglut_manage_tickets', 'number' => 100, 'exclude' => array( $uid ), 'fields' => array( 'ID', 'display_name' ) ) ) as $u ) { $out[ $u->ID ] = $u->display_name . ' (' . __( 'support', 'frontglut' ) . ')'; } foreach ( get_users( array( 'role' => 'administrator', 'number' => 20, 'exclude' => array( $uid ), 'fields' => array( 'ID', 'display_name' ) ) ) as $u ) { $out[ $u->ID ] = $u->display_name . ' (' . __( 'admin', 'frontglut' ) . ')'; } if ( class_exists( '\FrontGlut\Modules\Vendors\Vendors' ) ) { foreach ( get_users( array( 'meta_key' => 'frontglut_vendor_status', 'meta_value' => 'approved', 'number' => 200, 'exclude' => array( $uid ), 'fields' => 'ID' ) ) as $vid ) { $out[ $vid ] = \FrontGlut\Modules\Vendors\Vendors::store( $vid )['name'] . ' (' . __( 'store', 'frontglut' ) . ')'; } } // People who already wrote to me can always be answered. global $wpdb; foreach ( (array) $wpdb->get_col( $wpdb->prepare( "SELECT DISTINCT from_id FROM {$wpdb->prefix}frontglut_messages WHERE to_id = %d LIMIT 100", $uid ) ) as $fid ) { // phpcs:ignore WordPress.DB.PreparedSQL if ( $fid && ! isset( $out[ $fid ] ) && get_userdata( $fid ) ) { $out[ $fid ] = get_userdata( $fid )->display_name; } } return $out; } public function screen() { global $wpdb; $uid = get_current_user_id(); $a = Request::route( 'a', 'key', 'inbox' ); $id = Request::route( 'id', 'int', 0 ); $m = $wpdb->prefix . 'frontglut_messages'; $bar = Ui::btn( __( 'Compose', 'frontglut' ), Dashboard::url( 'messages', array( 'a' => 'new' ) ), 'frontglut-primary' ); if ( 'new' === $a ) { $opts = ''; $pre = Request::route( 'to', 'int', 0 ); foreach ( $this->recipients() as $k => $l ) { $opts .= ''; } $f = Ui::form_open( 'message_send', array( 'after' => 'redirect' ) ) . '
' . Fields::render( array( 'key' => 'att', 'type' => 'file', 'label' => __( 'Attachments', 'frontglut' ), 'multiple' => 1 ), array(), 'att' ) . Ui::form_close( __( 'Send', 'frontglut' ) ); return Ui::header( __( 'New message', 'frontglut' ), Ui::btn( __( '← Inbox', 'frontglut' ), Dashboard::url( 'messages' ) ) ) . '
' . $f . '
'; } if ( 'view' === $a && $id ) { $root = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $m WHERE id = %d", $id ), ARRAY_A ); // phpcs:ignore WordPress.DB.PreparedSQL if ( ! $root ) { return Ui::notice( esc_html__( 'Conversation not found.', 'frontglut' ), 'warn' ); } $tid = $root['thread_id'] ? (int) $root['thread_id'] : (int) $root['id']; $msgs = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM $m WHERE id = %d OR thread_id = %d ORDER BY id ASC", $tid, $tid ), ARRAY_A ); // phpcs:ignore WordPress.DB.PreparedSQL $ok = false; foreach ( $msgs as $x ) { if ( (int) $x['from_id'] === $uid || (int) $x['to_id'] === $uid ) { $ok = true; } } if ( ! $ok ) { return Ui::notice( esc_html__( 'Conversation not found.', 'frontglut' ), 'warn' ); } $wpdb->update( $m, array( 'is_read' => 1 ), array( 'to_id' => $uid, 'thread_id' => $tid ) ); $wpdb->update( $m, array( 'is_read' => 1 ), array( 'to_id' => $uid, 'id' => $tid ) ); $other = (int) $msgs[0]['from_id'] === $uid ? (int) $msgs[0]['to_id'] : (int) $msgs[0]['from_id']; $th = '
'; foreach ( $msgs as $x ) { $u = get_userdata( $x['from_id'] ); $att = ''; foreach ( array_filter( array_map( 'absint', explode( ',', (string) $x['attachments'] ) ) ) as $aid ) { $att .= '
📎 ' . esc_html( wp_basename( (string) get_attached_file( $aid ) ) ) . ''; } $th .= '
' . esc_html( ( $u ? $u->display_name : '?' ) . ' · ' . get_date_from_gmt( $x['created'], 'Y-m-d H:i' ) ) . '' . nl2br( esc_html( $x['body'] ) ) . $att . '
'; } $th .= '
'; $reply = Ui::form_open( 'message_send', array( 'after' => 'reload' ) ) . Ui::hidden( 'thread', $tid ) . Ui::hidden( 'to', $other ) . Ui::hidden( 'subject', 'Re: ' . $msgs[0]['subject'] ) . '
' . Fields::render( array( 'key' => 'att', 'type' => 'file', 'label' => __( 'Attachments', 'frontglut' ), 'multiple' => 1 ), array(), 'att' ) . Ui::form_close( __( 'Reply', 'frontglut' ) ); return Ui::header( $msgs[0]['subject'], Ui::btn( __( '← Inbox', 'frontglut' ), Dashboard::url( 'messages' ) ) ) . '
' . $th . '
' . $reply . '
'; } $box = 'sent' === $a ? 'sent' : 'inbox'; $col = 'sent' === $box ? 'from_id' : 'to_id'; // Root messages only (one row per conversation) involving me. $rs = $wpdb->get_results( $wpdb->prepare( "SELECT r.*, (SELECT COUNT(*) FROM $m u WHERE u.to_id = %d AND u.is_read = 0 AND (u.id = r.id OR u.thread_id = r.id)) unread, (SELECT MAX(created) FROM $m l WHERE l.id = r.id OR l.thread_id = r.id) last FROM $m r WHERE r.thread_id = 0 AND (r.from_id = %d OR r.to_id = %d OR EXISTS (SELECT 1 FROM $m x WHERE x.thread_id = r.id AND (x.from_id = %d OR x.to_id = %d))) ORDER BY last DESC LIMIT 100", $uid, $uid, $uid, $uid, $uid ), ARRAY_A ); // phpcs:ignore WordPress.DB.PreparedSQL $rows = array(); foreach ( $rs as $r ) { $other = (int) $r['from_id'] === $uid ? $r['to_id'] : $r['from_id']; if ( 'sent' === $box && (int) $r['from_id'] !== $uid ) { continue; } $ou = get_userdata( $other ); $rows[] = array( 'with' => esc_html( $ou ? $ou->display_name : '?' ), 'subject' => '' . ( $r['unread'] ? '' : '' ) . esc_html( $r['subject'] ) . ( $r['unread'] ? ' ' . Ui::badge( (string) $r['unread'], 'info' ) : '' ) . '', 'last' => Ui::date( $r['last'] ) ); } $pills = '
' . esc_html__( 'Inbox', 'frontglut' ) . '' . esc_html__( 'Started by me', 'frontglut' ) . '
'; return Ui::header( __( 'Messages', 'frontglut' ), $bar ) . $pills . Ui::table( array( 'with' => __( 'With', 'frontglut' ), 'subject' => __( 'Subject', 'frontglut' ), 'last' => __( 'Last activity', 'frontglut' ) ), $rows, array( 'empty' => __( 'No conversations yet.', 'frontglut' ) ) ); } public function ajax_send( $req ) { global $wpdb; $uid = get_current_user_id(); $to = (int) ( $req['to'] ?? 0 ); if ( ! get_userdata( $to ) || $to === $uid ) { Ajax::fail( __( 'Choose a recipient.', 'frontglut' ) ); } $thread = (int) ( $req['thread'] ?? 0 ); $allowed = array_keys( $this->recipients() ); if ( $thread ) { $root = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}frontglut_messages WHERE id = %d AND thread_id = 0", $thread ), ARRAY_A ); // phpcs:ignore WordPress.DB.PreparedSQL $part = $root && ( (int) $root['from_id'] === $uid || (int) $root['to_id'] === $uid ); if ( ! $part ) { $part = $root && (bool) $wpdb->get_var( $wpdb->prepare( "SELECT id FROM {$wpdb->prefix}frontglut_messages WHERE thread_id = %d AND (from_id = %d OR to_id = %d) LIMIT 1", $thread, $uid, $uid ) ); // phpcs:ignore WordPress.DB.PreparedSQL } if ( ! $part ) { Ajax::fail( __( 'Conversation not found.', 'frontglut' ), 404 ); } } elseif ( ! in_array( $to, $allowed, true ) ) { Ajax::fail( __( 'You cannot message this user.', 'frontglut' ), 403 ); } if ( ! RateLimiter::hit( 'msg', 30, HOUR_IN_SECONDS, 'u' . $uid ) ) { Ajax::fail( __( 'You are sending too fast.', 'frontglut' ), 429 ); } $body = sanitize_textarea_field( $req['body'] ?? '' ); $subj = sanitize_text_field( $req['subject'] ?? '' ); if ( '' === $body ) { Ajax::fail( __( 'Write a message.', 'frontglut' ) ); } $att = Uploads::filter_owned( array_filter( array_map( 'absint', (array) ( $req['att'] ?? array() ) ) ) ); $wpdb->insert( $wpdb->prefix . 'frontglut_messages', array( 'thread_id' => $thread, 'from_id' => $uid, 'to_id' => $to, 'subject' => substr( $subj ? $subj : __( '(no subject)', 'frontglut' ), 0, 200 ), 'body' => $body, 'attachments' => implode( ',', $att ), 'created' => current_time( 'mysql', true ) ) ); $id = (int) $wpdb->insert_id; Uploads::claim( $att, 0 ); $root = $thread ? $thread : $id; /* translators: %s: value. */ Notify::send( $to, sprintf( __( 'Message from %s', 'frontglut' ), wp_get_current_user()->display_name ), wp_trim_words( $body, 15 ), Dashboard::url( 'messages', array( 'a' => 'view', 'id' => $root ) ), 'msg' ); return array( 'message' => __( 'Message sent.', 'frontglut' ), 'redirect' => Dashboard::url( 'messages', array( 'a' => 'view', 'id' => $root ) ) ); } }