v = $v; } public function boot() { add_action( 'init', array( $this, 'rewrite' ) ); add_filter( 'query_vars', function ( $q ) { array_push( $q, 'frontglut_store', 'frontglut_spage' ); return $q; } ); add_action( 'template_redirect', array( $this, 'maybe_render' ), 5 ); add_shortcode( 'frontglut_stores', array( $this, 'sc_stores' ) ); add_action( 'woocommerce_single_product_summary', array( $this, 'sold_by' ), 6 ); add_action( 'woocommerce_after_shop_loop_item_title', array( $this, 'sold_by_loop' ), 12 ); add_filter( 'document_title_parts', function ( $p ) { $sl = get_query_var( 'frontglut_store' ); if ( $sl && ( $uid = Vendors::by_slug( $sl ) ) ) { $p['title'] = Vendors::store( $uid )['name']; } return $p; } ); Ajax::register( 'store_contact', array( $this, 'ajax_contact' ), array( 'public' => true ) ); } public function rewrite() { $base = preg_replace( '/[^a-z0-9\-_]/', '', strtolower( Settings::get( 'vendors.slug_base', 'store' ) ) ) ?: 'store'; add_rewrite_rule( '^' . $base . '/([^/]+)/page/([0-9]+)/?$', 'index.php?frontglut_store=$matches[1]&frontglut_spage=$matches[2]', 'top' ); add_rewrite_rule( '^' . $base . '/([^/]+)/?$', 'index.php?frontglut_store=$matches[1]', 'top' ); } /* ---------------- labels ---------------- */ public function sold_by() { global $product; $vid = $product ? Vendors::product_vendor( $product->get_id() ) : 0; if ( $vid ) { $s = Vendors::store( $vid ); echo '

' . esc_html__( 'Sold by', 'frontglut' ) . ' ' . esc_html( $s['name'] ) . '

'; } } public function sold_by_loop() { global $product; $vid = $product ? Vendors::product_vendor( $product->get_id() ) : 0; if ( $vid ) { echo '' . esc_html( Vendors::store( $vid )['name'] ) . ''; } } /* ---------------- helpers ---------------- */ private function open_now( array $hours ) { if ( ! $hours ) { return null; } $d = strtolower( substr( wp_date( 'D' ), 0, 3 ) ); $h = $hours[ $d ] ?? null; if ( ! $h || ! empty( $h['closed'] ) ) { return false; } $now = wp_date( 'H:i' ); return $now >= $h['open'] && $now <= $h['close']; } private function addr( array $s ) { return trim( implode( ', ', array_filter( array( $s['street'], $s['city'], trim( $s['state'] . ' ' . $s['zip'] ), $s['country'] ) ) ) ); } public function rating( $vid ) { global $wpdb; $r = $wpdb->get_row( $wpdb->prepare( "SELECT COUNT(*) c, AVG(m.meta_value) a FROM {$wpdb->comments} c JOIN {$wpdb->posts} p ON p.ID = c.comment_post_ID JOIN {$wpdb->commentmeta} m ON m.comment_id = c.comment_ID AND m.meta_key = 'rating' WHERE p.post_author = %d AND p.post_type = 'product' AND c.comment_approved = '1'", $vid ), ARRAY_A ); // phpcs:ignore WordPress.DB.PreparedSQL return array( (int) ( $r['c'] ?? 0 ), round( (float) ( $r['a'] ?? 0 ), 1 ) ); } /* ---------------- store page ---------------- */ public function maybe_render() { $slug = get_query_var( 'frontglut_store' ); if ( ! $slug ) { return; } $uid = Vendors::by_slug( $slug ); if ( ! $uid || ! Vendors::is_vendor( $uid ) ) { global $wp_query; $wp_query->set_404(); status_header( 404 ); return; } Assets::need(); $s = Vendors::store( $uid ); $tab = Request::route( 'tab', 'key', 'products' ); $pg = max( 1, (int) get_query_var( 'frontglut_spage' ) ); $q = Request::route( 'q', 'text', '' ); $banner = $s['banner'] ? wp_get_attachment_image_url( $s['banner'], 'large' ) : ''; $logo = $s['logo'] ? wp_get_attachment_image_url( $s['logo'], 'thumbnail' ) : get_avatar_url( $uid, array( 'size' => 120 ) ); list( $rc, $ra ) = $this->rating( $uid ); $open = $this->open_now( $s['hours'] ); $base = Vendors::store_url( $uid ); get_header(); do_action( 'frontglut_store_before', $uid ); echo '
'; echo '

' . esc_html( $s['name'] ) . '

' . ( $rc ? '★ ' . esc_html( $ra ) . ' (' . (int) $rc . ')' : esc_html__( 'No reviews yet', 'frontglut' ) ) . ( null !== $open ? ' · ' . ( $open ? '' . esc_html__( 'Open now', 'frontglut' ) . '' : '' . esc_html__( 'Closed', 'frontglut' ) . '' ) : '' ) . wp_kses_post( apply_filters( 'frontglut_store_badges', '', $uid ) ) . '
'; if ( ! empty( $s['vacation'] ) && ( empty( $s['vacation_until'] ) || strtotime( $s['vacation_until'] . ' 23:59:59' ) >= time() ) ) { echo wp_kses( Ui::notice( esc_html( $s['vacation_msg'] ? $s['vacation_msg'] : __( 'This store is on vacation.', 'frontglut' ) ), 'warn' ), Ui::allowed_html() ); } echo '
'; foreach ( apply_filters( 'frontglut_store_tabs', array( 'products' => __( 'Products', 'frontglut' ), 'about' => __( 'About & policies', 'frontglut' ), 'contact' => __( 'Contact', 'frontglut' ) ), $uid ) as $k => $l ) { echo '' . esc_html( $l ) . ''; } echo '
'; $extra = apply_filters( 'frontglut_store_tab_content', null, $tab, $uid ); if ( null !== $extra ) { echo '
' . wp_kses( (string) $extra, Ui::allowed_html() ) . '
'; } elseif ( 'about' === $tab ) { echo '
' . wp_kses_post( wpautop( $s['description'] ) ); $ad = $this->addr( $s ); echo '
' . ( $ad ? '
' . esc_html__( 'Address', 'frontglut' ) . '
' . esc_html( $ad ) . ( $s['map'] ? ' — ' . esc_html__( 'Map', 'frontglut' ) . '' : '' ) . '
' : '' ) . ( $s['phone'] ? '
' . esc_html__( 'Phone', 'frontglut' ) . '
' . esc_html( $s['phone'] ) . '
' : '' ); foreach ( array( 'website', 'facebook', 'twitter', 'instagram', 'youtube' ) as $soc ) { if ( $s[ $soc ] ) { echo '
' . esc_html( ucfirst( $soc ) ) . '
' . esc_html( $s[ $soc ] ) . '
'; } } echo '
'; if ( $s['hours'] ) { echo '

' . esc_html__( 'Opening hours', 'frontglut' ) . '

'; foreach ( array( 'mon' => 'Monday', 'tue' => 'Tuesday', 'wed' => 'Wednesday', 'thu' => 'Thursday', 'fri' => 'Friday', 'sat' => 'Saturday', 'sun' => 'Sunday' ) as $k => $l ) { $h = $s['hours'][ $k ] ?? array(); echo '
' . esc_html( $l ) . '
' . ( ! empty( $h['closed'] ) || ! $h ? esc_html__( 'Closed', 'frontglut' ) : esc_html( $h['open'] . ' – ' . $h['close'] ) ) . '
'; } echo '
'; } if ( $s['policy_shipping'] ) { echo '

' . esc_html__( 'Shipping policy', 'frontglut' ) . '

' . wp_kses_post( wpautop( $s['policy_shipping'] ) ); } if ( $s['policy_refund'] ) { echo '

' . esc_html__( 'Refund policy', 'frontglut' ) . '

' . wp_kses_post( wpautop( $s['policy_refund'] ) ); } echo '
'; } elseif ( 'contact' === $tab ) { echo wp_kses( '
' . Ui::form_open( 'store_contact', array( 'after' => 'reset' ) ) . Ui::hidden( 'store', $uid ) . \FrontGlut\Modules\Forms\Forms::honeypot(), Ui::allowed_html() ); echo wp_kses( \FrontGlut\Modules\Forms\Forms::render_fields( array( \FrontGlut\Modules\Products\Products::fd( 'name', 'text', __( 'Your name', 'frontglut' ), array( 'required' => 1, 'width' => '50' ) ), \FrontGlut\Modules\Products\Products::fd( 'email', 'email', __( 'Your email', 'frontglut' ), array( 'required' => 1, 'width' => '50' ) ), \FrontGlut\Modules\Products\Products::fd( 'message', 'textarea', __( 'Message', 'frontglut' ), array( 'required' => 1 ) ) ), is_user_logged_in() ? array( 'name' => wp_get_current_user()->display_name, 'email' => wp_get_current_user()->user_email ) : array(), 'frontglut_f' ), Ui::allowed_html() ); if ( Captcha::active_for( 'enquiry' ) ) { echo wp_kses( Captcha::render(), Ui::allowed_html() ); } echo wp_kses( Ui::form_close( __( 'Send message', 'frontglut' ) ) . '
', Ui::allowed_html() ); } else { $ids = get_posts( array( 'post_type' => 'product', 'author' => $uid, 'post_status' => 'publish', 'fields' => 'ids', 'numberposts' => 500, 's' => $q, 'orderby' => 'date', 'order' => 'DESC' ) ); echo '
'; $per = 12; $slice = array_slice( $ids, ( $pg - 1 ) * $per, $per ); echo wp_kses( $slice ? do_shortcode( '[products ids="' . implode( ',', $slice ) . '" columns="4" orderby="post__in"]' ) : '

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

', Ui::allowed_html() ); $pages = (int) ceil( count( $ids ) / $per ); if ( $pages > 1 ) { echo ''; } } echo '
'; get_footer(); exit; } public function ajax_contact( $req ) { $uid = (int) ( $req['store'] ?? 0 ); if ( ! Vendors::is_vendor( $uid ) ) { Ajax::fail( __( 'Store not found.', 'frontglut' ), 404 ); } if ( ! \FrontGlut\Modules\Forms\Forms::check_honeypot( $req ) ) { Ajax::fail( __( 'Spam detected.', 'frontglut' ) ); } if ( Captcha::active_for( 'enquiry' ) ) { $c = Captcha::verify( $req ); if ( is_wp_error( $c ) ) { Ajax::fail( $c->get_error_message() ); } } if ( ! RateLimiter::hit( 'storecontact', 5, HOUR_IN_SECONDS ) ) { Ajax::fail( __( 'Too many messages. Try later.', 'frontglut' ), 429 ); } $f = isset( $req['frontglut_f'] ) && is_array( $req['frontglut_f'] ) ? $req['frontglut_f'] : array(); $name = sanitize_text_field( $f['name'] ?? '' ); $mail = sanitize_email( $f['email'] ?? '' ); $msg = sanitize_textarea_field( $f['message'] ?? '' ); $err = array(); if ( '' === $name ) { $err['name'] = __( 'Required.', 'frontglut' ); } if ( ! is_email( $mail ) ) { $err['email'] = __( 'Enter a valid email.', 'frontglut' ); } if ( '' === $msg ) { $err['message'] = __( 'Required.', 'frontglut' ); } if ( $err ) { Ajax::fail( __( 'Please fix the highlighted fields.', 'frontglut' ), 400, array( 'errors' => $err ) ); } $s = Vendors::store( $uid ); $to = $s['email'] && is_email( $s['email'] ) ? $s['email'] : get_userdata( $uid )->user_email; if ( class_exists( '\FrontGlut\Modules\Engage\Tickets' ) ) { /* translators: %s: value. */ \FrontGlut\Modules\Engage\Tickets::create( 'enquiry', get_current_user_id(), $name, $mail, $uid, 0, sprintf( __( 'Message from %s', 'frontglut' ), $name ), $msg ); } /* translators: %s: value. */ Notify::send( $uid, sprintf( __( 'New message from %s', 'frontglut' ), $name ), wp_trim_words( $msg, 20 ), \FrontGlut\Modules\Dashboard\Dashboard::url( 'enquiries' ) ); /* translators: %s: value. */ Mailer::send( $to, sprintf( __( 'New message from %s', 'frontglut' ), $name ), $msg . "\n\n" . $name . ' <' . $mail . '>' ); return array( 'message' => __( 'Message sent. The store will reply by email.', 'frontglut' ) ); } /* ---------------- store list ---------------- */ public function sc_stores( $atts ) { Assets::need(); $a = shortcode_atts( array( 'per_page' => 12 ), $atts ); $q = Request::route( 'fs', 'text', '' ); $ord = 'name' === Request::route( 'fo' ) ? 'name' : 'new'; $pg = max( 1, (int) Request::route( 'sp', 'int', 1 ) ); $args = array( 'number' => (int) $a['per_page'], 'paged' => $pg, 'meta_query' => array( array( 'key' => 'frontglut_vendor_status', 'value' => 'approved' ) ), 'orderby' => 'registered', 'order' => 'DESC' ); if ( $q ) { $args['meta_query'][] = array( 'key' => 'frontglut_store', 'value' => $q, 'compare' => 'LIKE' ); } $uq = new \WP_User_Query( apply_filters( 'frontglut_stores_query_args', $args ) ); $list = $uq->get_results(); if ( 'name' === $ord ) { usort( $list, function ( $x, $y ) { return strcasecmp( Vendors::store( $x->ID )['name'], Vendors::store( $y->ID )['name'] ); } ); } $h = '
'; foreach ( $list as $u ) { $s = Vendors::store( $u->ID ); $b = $s['banner'] ? wp_get_attachment_image_url( $s['banner'], 'medium' ) : ''; $l = $s['logo'] ? wp_get_attachment_image_url( $s['logo'], 'thumbnail' ) : get_avatar_url( $u->ID, array( 'size' => 64 ) ); $h .= '
' . esc_html( $s['name'] ) . '
' . esc_html( $this->addr( $s ) ) . '
'; } $h .= $list ? '' : '

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

'; $h .= '
' . Ui::pager( $uq->get_total(), (int) $a['per_page'], $pg, array( 'fs' => $q, 'fo' => $ord ) ) . '
'; return str_replace( 'pg=', 'sp=', $h ); } }