boot(); ( new Messages() )->boot(); add_action( 'init', array( $this, 'register_cpts' ) ); add_filter( 'frontglut_menu_items', array( $this, 'menu' ) ); add_filter( 'frontglut_widgets', array( $this, 'widgets' ) ); add_filter( 'frontglut_topbar_html', array( $this, 'bell' ) ); add_filter( 'frontglut_managed_post_types', function ( $t ) { $t[] = 'frontglut_kb'; return $t; } ); add_filter( 'frontglut_can_submit', function ( $ok, $pt ) { return 'frontglut_kb' === $pt ? Caps::can( 'frontglut_moderate' ) : $ok; }, 10, 2 ); add_filter( 'frontglut_can_edit_post', function ( $ok, $post ) { return 'frontglut_kb' === $post->post_type ? Caps::can( 'frontglut_moderate' ) : $ok; }, 10, 2 ); add_filter( 'frontglut_post_status', function ( $s, $f, $uid, $pt ) { return 'frontglut_kb' === $pt ? 'publish' : $s; }, 10, 4 ); add_filter( 'frontglut_section_for_post_type', function ( $s, $pt ) { return 'frontglut_kb' === $pt ? 'kb' : $s; }, 10, 2 ); add_shortcode( 'frontglut_kb', array( $this, 'sc_kb' ) ); add_shortcode( 'frontglut_follow', array( $this, 'sc_follow' ) ); add_action( 'frontglut_daily', array( $this, 'digest' ) ); foreach ( array( 'notif_read', 'notif_delete', 'notice_send', 'notice_delete', 'follow_toggle' ) as $a ) { Ajax::register( $a, array( $this, 'ajax_' . $a ), array( 'cap' => 'notice_send' === $a || 'notice_delete' === $a ? 'frontglut_send_notices' : null ) ); } add_action( 'woocommerce_after_single_product_summary', function () { global $product; if ( $product && class_exists( '\FrontGlut\Modules\Vendors\Vendors' ) && ( $v = \FrontGlut\Modules\Vendors\Vendors::product_vendor( $product->get_id() ) ) ) { echo wp_kses( do_shortcode( '[frontglut_follow id="' . (int) $v . '" type="store"]' ), Ui::allowed_html() ); } }, 5 ); add_filter( 'the_content', function ( $c ) { if ( is_singular( 'post' ) && in_the_loop() && is_main_query() && get_post_meta( get_the_ID(), '_frontglut_form', true ) ) { $c .= do_shortcode( '[frontglut_follow id="' . (int) get_post_field( 'post_author', get_the_ID() ) . '" type="author"]' ); } return $c; } ); } public function register_cpts() { register_post_type( 'frontglut_kb', array( 'labels' => array( 'name' => __( 'Knowledge base', 'frontglut' ), 'singular_name' => __( 'Article', 'frontglut' ) ), 'public' => true, 'has_archive' => false, 'show_in_rest' => true, 'supports' => array( 'title', 'editor', 'thumbnail' ), 'rewrite' => array( 'slug' => 'help' ), 'show_in_menu' => 'frontglut', 'taxonomies' => array( 'frontglut_kb_cat' ) ) ); register_taxonomy( 'frontglut_kb_cat', 'frontglut_kb', array( 'labels' => array( 'name' => __( 'KB categories', 'frontglut' ) ), 'hierarchical' => true, 'public' => true, 'show_in_rest' => true, 'rewrite' => array( 'slug' => 'help-topic' ) ) ); register_post_type( 'frontglut_notice', array( 'labels' => array( 'name' => 'Announcements' ), 'public' => false, 'supports' => array( 'title', 'editor' ) ) ); if ( ! get_option( 'frontglut_kb_form' ) || ! Forms::get( (int) get_option( 'frontglut_kb_form' ) ) ) { $id = Forms::save( array( 'type' => 'post', 'title' => 'Knowledge base article', 'settings' => Forms::post_settings( array( 'post_type' => 'frontglut_kb', 'status' => 'publish', 'editor' => 'lite', 'notify_admin' => 0, 'notify_user' => 0 ) ), 'fields' => array( Forms::f( 'title', 'text', __( 'Title', 'frontglut' ), array( 'required' => 1, 'storage' => 'post_title' ) ), Forms::f( 'content', 'editor', __( 'Answer', 'frontglut' ), array( 'required' => 1, 'storage' => 'post_content' ) ), Forms::f( 'topic', 'taxonomy', __( 'Topic', 'frontglut' ), array( 'storage' => 'tax', 'taxonomy' => 'frontglut_kb_cat', 'ui' => 'checkboxes' ) ), ) ) ); update_option( 'frontglut_kb_form', $id, false ); } } public function menu( $i ) { $i['notifications'] = array( 'label' => __( 'Notifications', 'frontglut' ), 'icon' => 'bell', 'group' => 'comms', 'order' => 60, 'render' => array( $this, 'screen_notifications' ), 'badge' => array( 'FrontGlut\\Core\\Notify', 'unread' ) ); $i['notices'] = array( 'label' => __( 'Announcements', 'frontglut' ), 'icon' => 'megaphone', 'group' => 'comms', 'order' => 64, 'cap' => 'frontglut_send_notices', 'render' => array( $this, 'screen_notices' ) ); $i['kb'] = array( 'label' => __( 'Knowledge base', 'frontglut' ), 'icon' => 'book', 'group' => 'comms', 'order' => 65, 'cap' => 'frontglut_moderate', 'render' => function () { $posts = \FrontGlut\Core\Plugin::instance()->modules()->get( 'posts' ); return $posts ? $posts->manage_screen( 'kb', 'frontglut_kb', __( 'Knowledge base', 'frontglut' ), (int) get_option( 'frontglut_kb_form' ) ) : ''; } ); return $i; } public function widgets( $w ) { $w['announcements'] = array( 'title' => __( 'Announcements', 'frontglut' ), 'order' => 3, 'size' => 2, 'render' => array( $this, 'widget_notices' ) ); $w['recent_notifs'] = array( 'title' => __( 'Recent notifications', 'frontglut' ), 'order' => 45, 'size' => 1, 'render' => function () { $n = Notify::latest( get_current_user_id(), 5 ); if ( ! $n ) { return '

' . esc_html__( 'You are all caught up.', 'frontglut' ) . '

'; } $h = '

' . Ui::btn( __( 'All notifications', 'frontglut' ), Dashboard::url( 'notifications' ), 'frontglut-sm' ) . '

'; } ); return $w; } public function bell( $h ) { $n = Notify::unread(); return $h . '' . Ui::icon( 'bell' ) . ( $n ? '' . (int) min( 99, $n ) . '' : '' ) . ''; } /* ---------------- notifications ---------------- */ public function screen_notifications() { $uid = get_current_user_id(); $pg = Request::page(); $list = Notify::latest( $uid, 20, ( $pg - 1 ) * 20 ); global $wpdb; $tot = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->prefix}frontglut_notifications WHERE user_id = %d", $uid ) ); // phpcs:ignore WordPress.DB.PreparedSQL $rows = array(); foreach ( $list as $n ) { $rows[] = array( 'msg' => ( $n['is_read'] ? '' : '' ) . ( $n['url'] ? '' . esc_html( $n['title'] ) . '' : esc_html( $n['title'] ) ) . ( $n['is_read'] ? '' : '' ) . ( $n['message'] ? '
' . esc_html( $n['message'] ) . '' : '' ), 'when' => Ui::date( $n['created'] ), 'act' => ( $n['is_read'] ? '' : Ui::action_btn( __( 'Mark read', 'frontglut' ), 'notif_read', array( 'id' => $n['id'] ) ) ) . Ui::action_btn( __( 'Delete', 'frontglut' ), 'notif_delete', array( 'id' => $n['id'] ), 'frontglut-danger', false, 'remove' ) ); } return Ui::header( __( 'Notifications', 'frontglut' ), Ui::action_btn( __( 'Mark all as read', 'frontglut' ), 'notif_read', array( 'id' => 0 ) ) ) . Ui::table( array( 'msg' => __( 'Notification', 'frontglut' ), 'when' => __( 'When', 'frontglut' ), 'act' => '' ), $rows, array( 'empty' => __( 'No notifications.', 'frontglut' ) ) ) . Ui::pager( $tot, 20, $pg ); } public function ajax_notif_read( $req ) { Notify::mark_read( get_current_user_id(), (int) ( $req['id'] ?? 0 ) ); return array(); } public function ajax_notif_delete( $req ) { global $wpdb; $wpdb->delete( $wpdb->prefix . 'frontglut_notifications', array( 'id' => (int) ( $req['id'] ?? 0 ), 'user_id' => get_current_user_id() ) ); wp_cache_delete( 'frontglut_unread_' . get_current_user_id(), 'frontglut' ); return array(); } /* ---------------- announcements ---------------- */ public function widget_notices() { $u = wp_get_current_user(); $q = get_posts( array( 'post_type' => 'frontglut_notice', 'post_status' => 'publish', 'numberposts' => 20, 'orderby' => 'date', 'order' => 'DESC' ) ); $h = ''; $shown = 0; foreach ( $q as $p ) { $roles = (array) get_post_meta( $p->ID, '_frontglut_roles', true ); if ( $roles && ! array_intersect( $roles, (array) $u->roles ) && ! current_user_can( 'manage_options' ) ) { continue; } $h .= '
' . esc_html( $p->post_title ) . '
' . wp_kses_post( wpautop( $p->post_content ) ) . '' . esc_html( get_the_date( '', $p ) ) . '
'; if ( ++$shown >= 3 ) { break; } } return $h; } public function screen_notices() { $roles = '
'; foreach ( wp_roles()->roles as $k => $r ) { $roles .= ''; } $roles .= '
'; $f = Ui::form_open( 'notice_send', array( 'after' => 'reload' ) ) . '
' . $roles . '
' . Ui::form_close( __( 'Publish announcement', 'frontglut' ) ); $rows = array(); foreach ( get_posts( array( 'post_type' => 'frontglut_notice', 'post_status' => 'publish', 'numberposts' => 30 ) ) as $p ) { $rows[] = array( 'title' => esc_html( $p->post_title ), 'aud' => esc_html( implode( ', ', (array) get_post_meta( $p->ID, '_frontglut_roles', true ) ) ?: __( 'Everyone', 'frontglut' ) ), 'date' => Ui::date( $p->post_date ), 'act' => Ui::action_btn( __( 'Delete', 'frontglut' ), 'notice_delete', array( 'id' => $p->ID ), 'frontglut-danger', true ) ); } return Ui::header( __( 'Announcements', 'frontglut' ) ) . '
' . $f . '
' . Ui::table( array( 'title' => __( 'Announcement', 'frontglut' ), 'aud' => __( 'Audience', 'frontglut' ), 'date' => __( 'Date', 'frontglut' ), 'act' => '' ), $rows, array( 'empty' => __( 'No announcements yet.', 'frontglut' ) ) ); } public function ajax_notice_send( $req ) { $title = sanitize_text_field( $req['title'] ?? '' ); $msg = sanitize_textarea_field( $req['message'] ?? '' ); if ( '' === $title || '' === $msg ) { Ajax::fail( __( 'Title and message are required.', 'frontglut' ) ); } $roles = array_values( array_intersect( array_map( 'sanitize_key', (array) ( $req['roles'] ?? array() ) ), array_keys( wp_roles()->roles ) ) ); $id = wp_insert_post( array( 'post_type' => 'frontglut_notice', 'post_status' => 'publish', 'post_title' => $title, 'post_content' => $msg ) ); update_post_meta( $id, '_frontglut_roles', $roles ); $args = array( 'fields' => array( 'ID', 'user_email' ), 'number' => 500 ); if ( $roles ) { $args['role__in'] = $roles; } $n = 0; if ( ! empty( $req['push'] ) || ! empty( $req['email'] ) ) { foreach ( get_users( $args ) as $u ) { if ( ! empty( $req['push'] ) ) { Notify::send( $u->ID, $title, wp_trim_words( $msg, 20 ), Dashboard::url(), 'announce' ); } if ( ! empty( $req['email'] ) ) { Mailer::send( $u->user_email, $title, $msg ); } $n++; } } Audit::log( 'announcement', (string) $id, "$n recipients" ); /* translators: %s: value. */ return array( 'message' => sprintf( __( 'Published to %d recipients.', 'frontglut' ), $n ) ); } public function ajax_notice_delete( $req ) { $p = get_post( (int) ( $req['id'] ?? 0 ) ); if ( $p && 'frontglut_notice' === $p->post_type ) { wp_delete_post( $p->ID, true ); } return array( 'message' => __( 'Deleted.', 'frontglut' ) ); } /* ---------------- followers ---------------- */ public function sc_follow( $atts ) { $a = shortcode_atts( array( 'id' => 0, 'type' => 'author' ), $atts ); $id = (int) $a['id']; if ( ! $id || ! get_userdata( $id ) ) { return ''; } Assets::need(); $type = 'store' === $a['type'] ? 'store' : 'author'; if ( ! is_user_logged_in() ) { return '

' . esc_html( 'store' === $type ? __( 'Log in to follow this store', 'frontglut' ) : __( 'Log in to follow this author', 'frontglut' ) ) . '

'; } global $wpdb; $on = (bool) $wpdb->get_var( $wpdb->prepare( "SELECT id FROM {$wpdb->prefix}frontglut_follows WHERE user_id = %d AND target_id = %d AND type = %s", get_current_user_id(), $id, $type ) ); // phpcs:ignore WordPress.DB.PreparedSQL $cnt = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->prefix}frontglut_follows WHERE target_id = %d AND type = %s", $id, $type ) ); // phpcs:ignore WordPress.DB.PreparedSQL /* translators: %s: value. */ return '

' . esc_html( sprintf( _n( '%d follower', '%d followers', $cnt, 'frontglut' ), $cnt ) ) . '

'; } public function ajax_follow_toggle( $req ) { global $wpdb; $id = (int) ( $req['id'] ?? 0 ); $type = 'store' === ( $req['type'] ?? '' ) ? 'store' : 'author'; $uid = get_current_user_id(); if ( ! $id || $id === $uid || ! get_userdata( $id ) ) { Ajax::fail( __( 'Invalid request.', 'frontglut' ) ); } $t = $wpdb->prefix . 'frontglut_follows'; if ( $wpdb->get_var( $wpdb->prepare( "SELECT id FROM $t WHERE user_id = %d AND target_id = %d AND type = %s", $uid, $id, $type ) ) ) { // phpcs:ignore WordPress.DB.PreparedSQL $wpdb->delete( $t, array( 'user_id' => $uid, 'target_id' => $id, 'type' => $type ) ); return array( 'message' => __( 'Unfollowed.', 'frontglut' ) ); } $wpdb->insert( $t, array( 'user_id' => $uid, 'target_id' => $id, 'type' => $type, 'created' => current_time( 'mysql', true ) ) ); /* translators: %s: value. */ Notify::send( $id, sprintf( __( '%s followed you', 'frontglut' ), wp_get_current_user()->display_name ), '', Dashboard::url() ); return array( 'message' => __( 'Following!', 'frontglut' ) ); } /** Daily digest of new products/posts from followed stores/authors. */ public function digest() { global $wpdb; $since = gmdate( 'Y-m-d H:i:s', time() - DAY_IN_SECONDS ); $rows = $wpdb->get_results( "SELECT user_id, target_id, type FROM {$wpdb->prefix}frontglut_follows", ARRAY_A ); // phpcs:ignore WordPress.DB.PreparedSQL $by = array(); foreach ( $rows as $r ) { $by[ $r['user_id'] ][] = $r; } foreach ( $by as $uid => $follows ) { $lines = array(); foreach ( $follows as $f ) { $posts = get_posts( array( 'author' => $f['target_id'], 'post_type' => 'store' === $f['type'] ? 'product' : 'post', 'post_status' => 'publish', 'date_query' => array( array( 'after' => $since, 'column' => 'post_date_gmt' ) ), 'numberposts' => 5 ) ); foreach ( $posts as $p ) { $lines[] = '• ' . get_the_title( $p ) . ' — ' . get_permalink( $p ); } } $u = get_userdata( $uid ); if ( $lines && $u ) { /* translators: %s: value. */ Mailer::send( $u->user_email, sprintf( __( 'New from the stores and authors you follow — %s', 'frontglut' ), get_bloginfo( 'name' ) ), implode( "\n", $lines ) ); } } } /* ---------------- knowledge base ---------------- */ public function sc_kb( $atts ) { Assets::need(); $q = Request::route( 'kq', 'text', '' ); $cat = Request::route( 'kc', 'int', 0 ); $args = array( 'post_type' => 'frontglut_kb', 'post_status' => 'publish', 'posts_per_page' => 30, 's' => $q ); if ( $cat ) { $args['tax_query'] = array( array( 'taxonomy' => 'frontglut_kb_cat', 'terms' => array( $cat ) ) ); } $h = '
'; $h .= '' . esc_html__( 'All', 'frontglut' ) . ''; foreach ( (array) get_terms( array( 'taxonomy' => 'frontglut_kb_cat', 'hide_empty' => true ) ) as $t ) { if ( ! is_wp_error( $t ) ) { $h .= '' . esc_html( $t->name ) . ''; } } $h .= '
'; $posts = get_posts( $args ); foreach ( $posts as $p ) { $h .= '

' . esc_html( get_the_title( $p ) ) . '

' . esc_html( wp_trim_words( wp_strip_all_tags( $p->post_content ), 30 ) ) . '

'; } return $h . ( $posts ? '' : '

' . esc_html__( 'No articles found.', 'frontglut' ) . '

' ) . '
'; } }