__( '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 = '';
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 ) ) );
}
}