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 );
}
}