control_groups['query'] = [ 'title' => esc_html__('Query', 'fluent-cart'), 'tab' => 'content', ]; $this->control_groups['fields'] = [ 'title' => esc_html__('Fields', 'fluent-cart'), 'tab' => 'content', ]; $this->control_groups['display'] = [ 'title' => esc_html__('Display', 'fluent-cart'), 'tab' => 'content', ]; $this->control_groups['filter'] = [ 'title' => esc_html__('Filter', 'fluent-cart'), 'tab' => 'content', ]; $this->control_groups['widgets'] = [ 'title' => esc_html__('Widgets', 'fluent-cart'), 'tab' => 'widgets', ]; } public function set_controls() { // LAYOUT $this->controls['viewMode'] = [ 'tab' => 'content', 'group' => 'display', 'label' => esc_html__('View Mode', 'fluent-cart'), 'type' => 'select', 'options' => [ 'grid' => esc_html__('Grid', 'fluent-cart'), 'list' => esc_html__('List', 'fluent-cart'), ], 'placeholder' => esc_html__('Grid', 'fluent-cart'), 'rerender' => true, ]; $this->controls['showViewSwitcher'] = [ 'tab' => 'content', 'group' => 'display', 'label' => esc_html__('Show View Switcher', 'fluent-cart'), 'type' => 'checkbox', 'inline' => true, 'default' => true, ]; $this->controls['paginationType'] = [ 'tab' => 'content', 'group' => 'display', 'label' => esc_html__('Pagination Type', 'fluent-cart'), 'type' => 'select', 'options' => [ 'scroll' => esc_html__('Scroll', 'fluent-cart'), 'numbers' => esc_html__('Numbers', 'fluent-cart'), ], 'placeholder' => esc_html__('Numbers', 'fluent-cart'), ]; $this->controls['columns'] = [ 'tab' => 'content', 'label' => esc_html__('Columns', 'fluent-cart'), 'type' => 'number', 'min' => 1, 'max' => 5, 'breakpoints' => true, 'placeholder' => 4, 'css' => [ [ 'selector' => $this->cssRoot, 'property' => '--grid-columns', ], ], ]; $this->controls['gap'] = [ 'tab' => 'content', 'label' => esc_html__('Gap', 'fluent-cart'), 'type' => 'number', 'units' => true, 'css' => [ [ 'selector' => $this->cssRoot, 'property' => 'gap', ], ], 'placeholder' => 30, ]; $this->controls['posts_per_page'] = [ 'tab' => 'content', 'label' => esc_html__('Products per page', 'fluent-cart'), 'type' => 'number', 'min' => 1, 'max' => 100, 'placeholder' => 10, 'step' => 1, ]; $this->controls['is_main_query'] = [ 'tab' => 'content', 'label' => esc_html__('Is main query', 'fluent-cart'), 'type' => 'checkbox', 'inline' => true, ]; // QUERY $this->controls['orderby'] = [ 'tab' => 'content', 'label' => esc_html__('Order by', 'fluent-cart'), 'type' => 'select', // id|date|title|price 'options' => [ 'price' => esc_html__('Price', 'fluent-cart'), 'title' => esc_html__('Product Name', 'fluent-cart'), 'date' => esc_html__('Published date', 'fluent-cart'), 'id' => esc_html__('Product ID', 'fluent-cart') ], 'inline' => true, 'placeholder' => esc_html__('Default', 'fluent-cart'), ]; $this->controls['order'] = [ 'tab' => 'content', 'label' => esc_html__('Order', 'fluent-cart'), 'type' => 'select', 'options' => [ 'ASC' => esc_html__('Ascending', 'fluent-cart'), 'DESC' => esc_html__('Descending', 'fluent-cart'), ], 'inline' => true, 'placeholder' => esc_html__('Descending', 'fluent-cart'), ]; $this->controls['main_query_info'] = [ 'tab' => 'content', 'group' => 'query', 'type' => 'info', 'content' => esc_html__('The query settings will be ignored when Is main query is enabled.', 'fluent-cart'), ]; $this->controls['productType'] = [ 'tab' => 'content', 'group' => 'query', 'label' => esc_html__('Product type', 'fluent-cart'), 'type' => 'select', // physical|digital|subscription|onetime|simple|variations 'options' => [ 'simple' => esc_html__('Simple', 'fluent-cart'), 'physical' => esc_html__('Physical', 'fluent-cart'), 'digital' => esc_html__('Digital', 'fluent-cart'), 'variations' => esc_html__('Variations', 'fluent-cart'), 'ontime' => esc_html__('One-time', 'fluent-cart'), 'subscription' => esc_html__('Subscriptions', 'fluent-cart'), ], 'multiple' => false, 'placeholder' => esc_html__('all product types', 'fluent-cart'), ]; $this->controls['include'] = [ 'tab' => 'content', 'group' => 'query', 'label' => esc_html__('Include', 'fluent-cart'), 'type' => 'select', 'optionsAjax' => [ 'action' => 'bricks_get_posts', 'postType' => 'fluent-products', ], 'multiple' => true, 'searchable' => true, 'placeholder' => esc_html__('Select products', 'fluent-cart'), ]; //phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_exclude $this->controls['exclude'] = [ 'tab' => 'content', 'group' => 'query', 'label' => esc_html__('Exclude', 'fluent-cart'), 'type' => 'select', 'optionsAjax' => [ 'action' => 'bricks_get_posts', 'postType' => 'fluent-products', ], 'multiple' => true, 'searchable' => true, 'placeholder' => esc_html__('Select products', 'fluent-cart'), ]; $this->controls['categories'] = [ 'tab' => 'content', 'group' => 'query', 'label' => esc_html__('Product categories', 'fluent-cart'), 'type' => 'select', 'options' => BricksHelper::getCategoriesOptions(), 'multiple' => true, ]; $this->controls['onSale'] = [ 'tab' => 'content', 'group' => 'query', 'label' => esc_html__('On sale Products only', 'fluent-cart'), 'type' => 'checkbox', ]; $this->controls['allowOutOfStock'] = [ 'tab' => 'content', 'group' => 'query', 'label' => esc_html__('Allow Out Of Stock', 'fluent-cart'), 'type' => 'checkbox', ]; // FILTER $this->controls['enableFilter'] = [ 'tab' => 'content', 'group' => 'filter', 'label' => esc_html__('Enable Filter', 'fluent-cart'), 'type' => 'checkbox', 'inline' => true, ]; $this->controls['enableSortBy'] = [ 'tab' => 'content', 'group' => 'filter', 'label' => esc_html__('Enable Sort By', 'fluent-cart'), 'type' => 'checkbox', 'inline' => true, 'default' => true, 'required' => ['enableFilter', '=', true], ]; $this->controls['liveFilter'] = [ 'tab' => 'content', 'group' => 'filter', 'label' => esc_html__('Live Filter', 'fluent-cart'), 'type' => 'checkbox', 'inline' => true, 'required' => ['enableFilter', '=', true], ]; $this->controls['wildcardFilter'] = [ 'tab' => 'content', 'group' => 'filter', 'label' => esc_html__('Wildcard Filter', 'fluent-cart'), 'type' => 'checkbox', 'inline' => true, 'required' => ['enableFilter', '=', true], ]; $this->controls['productCategories'] = [ 'tab' => 'content', 'group' => 'filter', 'label' => esc_html__( 'Product Categories', 'fluent-cart' ), 'type' => 'checkbox', 'inline' => true, 'required' => ['enableFilter', '=', true], ]; $this->controls['displayNameCategories'] = [ 'tab' => 'content', 'group' => 'filter', 'label' => esc_html__('Display Name', 'fluent-cart'), 'type' => 'text', 'placeholder' => esc_html__('Custom filter label', 'fluent-cart'), 'required' => [ ['enableFilter', '=', true], ['productCategories', '=', true], ], ]; $this->controls['productBrands'] = [ 'tab' => 'content', 'group' => 'filter', 'label' => esc_html__( 'Product Brands', 'fluent-cart' ), 'type' => 'checkbox', 'inline' => true, 'required' => ['enableFilter', '=', true], ]; $this->controls['displayNameBrands'] = [ 'tab' => 'content', 'group' => 'filter', 'label' => esc_html__('Display Name', 'fluent-cart'), 'type' => 'text', 'placeholder' => esc_html__('Custom filter label', 'fluent-cart'), 'required' => [ ['enableFilter', '=', true], ['productBrands', '=', true], ], ]; $this->controls['priceRange'] = [ 'tab' => 'content', 'group' => 'filter', 'label' => esc_html__( 'Price Range', 'fluent-cart' ), 'type' => 'checkbox', 'inline' => true, 'required' => ['enableFilter', '=', true], ]; $this->controls['displayNamePriceRange'] = [ 'tab' => 'content', 'group' => 'filter', 'label' => esc_html__('Display Name', 'fluent-cart'), 'type' => 'text', 'placeholder' => esc_html__('Custom filter label', 'fluent-cart'), 'required' => [ ['enableFilter', '=', true], ['priceRange', '=', true], ], ]; // FIELDS $fields = $this->get_post_fields(); // Remove field settings unset($fields['fields']['fields']['overlay']); unset($fields['fields']['fields']['dynamicPadding']); unset($fields['fields']['fields']['dynamicBackground']); unset($fields['fields']['fields']['dynamicBorder']); // Set fields defaults fields set $fields['fields']['default'] = [ [ 'dynamicData' => '{fct_product_image:link}', 'id' => Helpers::generate_random_id(false), ], [ 'dynamicData' => '{fct_product_title:linked}', 'tag' => 'h5', 'id' => Helpers::generate_random_id(false), ], [ 'dynamicData' => '{fct_product_excerpt}', 'tag' => 'p', 'id' => Helpers::generate_random_id(false), ], [ 'dynamicData' => '{fct_product_price}', 'id' => Helpers::generate_random_id(false), ], [ 'dynamicData' => '{fct_product_button}', 'id' => Helpers::generate_random_id(false), ] ]; $this->controls = array_replace_recursive($this->controls, $fields); $this->controls['linkProduct'] = [ 'tab' => 'content', 'group' => 'fields', 'label' => esc_html__('Link entire product', 'fluent-cart'), 'type' => 'checkbox', 'inline' => true, 'description' => esc_html__('Only added if none of your product fields contains any links.', 'fluent-cart'), ]; } public function render() { $settings = $this->settings; $this->setBricksQuery(); $viewMode = Arr::get($settings, 'viewMode', 'grid'); $showViewSwitcher = !empty($settings['showViewSwitcher']); $paginationType = Arr::get($settings, 'paginationType', 'numbers'); $isMainQuery = Arr::get($settings, 'is_main_query', false) && $this->is_frontend; $perPage = (int)Arr::get($settings, 'posts_per_page', 10); $enableFilter = !empty($settings['enableFilter']); $priceRange = !empty($settings['priceRange']); $liveFilter = !empty($settings['liveFilter']); $this->storeSettingsTransient($settings); if ($perPage <= 0) { $perPage = 10; } if ($perPage > 100) { $perPage = 100; } $args = $this->buildQueryArgs( $settings, $isMainQuery, $perPage, $viewMode ); $productsQuery = (new ProductQuery($args)); $products = $productsQuery->get(); $defaultFilters = $productsQuery->getDefaultFilters(); $ajaxDefaultFilters = $this->getAjaxDefaultFilters( $defaultFilters, $settings ); $filters = $this->getFilters($settings); $wrapperClass = 'fct-products-wrapper-inner ' . ($viewMode === 'list' ? 'mode-list' : 'mode-grid') . (!$enableFilter ? ' fct-full-container-width' : ''); $wrapperAttributes = [ 'class' => $wrapperClass, 'data-fluent-cart-product-wrapper-inner' => '', 'data-per-page' => $perPage, 'data-order-type' => Arr::get($defaultFilters, 'sort_type'), 'data-live-filter' => $liveFilter, 'data-paginator' => esc_attr($paginationType), 'data-default-filters' => wp_json_encode($ajaxDefaultFilters) ]; $productWrapperClasses = [ 'fct-products-wrapper', 'fct-brick-products-wrapper', ]; if (!$this->is_frontend) { $productWrapperClasses[] = 'fct-bricks-editor-mode'; } $rendererConfig = [ 'view_mode' => $viewMode, 'enable_wildcard_filter' => !empty($settings['wildcardFilter']), 'custom_filters' => [ 'enabled' => $enableFilter, 'price_range' => $priceRange, 'live_filter' => $liveFilter ], ]; // Only override enable_sort_by if explicitly set in settings (key exists) if (array_key_exists('enableSortBy', $settings)) { $rendererConfig['enable_sort_by'] = !empty($settings['enableSortBy']); } $shopAppRenderer = new ShopAppRenderer($products, $rendererConfig); ?>
render_attributes('_root'); ?> >
render_attributes('wrapper'); ?> >
renderViewSwitcher(); } ?>
> renderFilter($productFilterRenderer); } ?>
renderProducts($products); ?>
renderPagination( $products, $defaultFilters, $paginationType, $perPage, $viewMode ); } ?>
uid; if (!get_transient($uuid)) { // save the settings as transient set_transient($uuid, $settings, 48 * HOUR_IN_SECONDS); } } /** * Build query args. */ private function buildQueryArgs($settings, $isMainQuery, $perPage, $viewMode) { $args = array_filter([ 'paginate' => 'simple', 'is_main_query' => $isMainQuery, 'sort_by' => Arr::get($settings, 'orderby', 'date'), 'sort_type' => Arr::get($settings, 'order', 'desc'), 'per_page' => $perPage, 'view_mode' => $viewMode, ]); if (!$isMainQuery) { $includeIds = Arr::get($settings, 'include', []); if ($includeIds) { $args['include_ids'] = $includeIds; } $excludeIds = Arr::get($settings, 'exclude', []); if ($excludeIds) { $args['exclude_ids'] = $excludeIds; } $productType = Arr::get($settings, 'productType', []); if ($productType) { $args['product_type'] = $productType; } $onSale = Arr::get($settings, 'onSale', false); if ($onSale) { $args['on_sale'] = true; } $allowOutOfStock = Arr::get($settings, 'allowOutOfStock', false); if ($allowOutOfStock) { $args['allow_out_of_stock'] = true; } $categories = Arr::get($settings, 'categories', []); if ($categories) { //phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query $args['tax_query'] = [ 'product-categories' => $categories, ]; } } return $args; } /** * Render product loop. */ private function renderProducts($products) { ProductDataSetup::setProductsCache($products); $postIndex = 1; foreach ($products as $product) { $post = get_post($product->ID); setup_postdata($post); $this->set_loop_object($post); $this->render_fields($post, $postIndex); $this->next_iteration(); $postIndex++; } wp_reset_postdata(); $this->end_iteration(); } /** * Render pagination. */ private function renderPagination( $products, $defaultFilters, $paginationType, $perPage, $viewMode ) { $renderer = new ShopAppRenderer( [ 'products' => $products, 'total' => $products->total(), ], [ 'default_filters' => $defaultFilters, 'pagination_type' => $paginationType, 'per_page' => $perPage, 'view_mode' => $viewMode, ] ); $renderer->renderPaginator(); } /** * Get available product filters. */ private function getFilters(array $settings): array { $categoriesLabel = !empty($settings['displayNameCategories']) ? $settings['displayNameCategories'] : __('Product Categories', 'fluent-cart'); $brandsLabel = !empty($settings['displayNameBrands']) ? $settings['displayNameBrands'] : __('Product Brands', 'fluent-cart'); $priceRangeLabel = !empty($settings['displayNamePriceRange']) ? $settings['displayNamePriceRange'] : __('Price', 'fluent-cart'); $filters = [ 'product-categories' => [ 'filter_type' => 'options', 'is_meta' => true, 'label' => $categoriesLabel, 'enabled' => !empty($settings['productCategories']), 'multiple' => false, ], 'product-brands' => [ 'filter_type' => 'options', 'is_meta' => true, 'label' => $brandsLabel, 'enabled' => !empty($settings['productBrands']), 'multiple' => false, ], 'price_range' => [ 'filter_type' => 'range', 'is_meta' => false, 'label' => $priceRangeLabel, 'enabled' => !empty($settings['priceRange']) ], ]; return $filters; } private function setBricksQuery() { $query_object = new Query( [ 'id' => $this->id, 'name' => $this->name, 'settings' => $this->settings, ] ); // Set $bricks_query (@since 1.10.2) $this->set_bricks_query($query_object); $this->start_iteration(); } public function render_fields($post, $post_index) { BricksHelper::renderCollectionCard($this->settings, $post, $post_index, $this->uid); } public function renderAjaxContents($products, $settings) { $this->settings = $settings; $this->setBricksQuery(); ProductDataSetup::setProductsCache($products); $postIndex = 1; foreach ($products as $product) { $post = get_post($product->ID); setup_postdata($post); $this->set_loop_object($post); $this->render_fields($post, $postIndex); $this->next_iteration(); $postIndex++; } wp_reset_postdata(); $this->end_iteration(); } private function getAjaxDefaultFilters(array $defaultFilters, array $settings): array { $filters = []; $taxQuery = Arr::get($defaultFilters, 'tax_query', []); if (!empty($taxQuery)) { foreach ($taxQuery as $taxonomy => $termIds) { $filters[$taxonomy] = $termIds; } } $allowOutOfStock = Arr::get($settings, 'allowOutOfStock', false); if ($allowOutOfStock) { $filters['allow_out_of_stock'] = true; } if (!empty($filters)) { $filters['enabled'] = true; } return $filters; } }