PluginProbe ʕ •ᴥ•ʔ
FrontBlocks for Gutenberg/GeneratePress / ci-artifacts
FrontBlocks for Gutenberg/GeneratePress vci-artifacts
trunk 0.2.0 0.2.1 0.2.2 0.2.3 0.2.4 0.2.5 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.1.0 1.2.0 1.2.1 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.3.5 1.3.6 ci-artifacts
frontblocks / includes / Frontend / ProductCategories.php
frontblocks / includes / Frontend Last commit date
Animations.php 1 month ago BackButton.php 7 months ago BeforeAfter.php 1 month ago BlockPatterns.php 4 months ago Carousel.php 1 month ago ContainerEdgeAlignment.php 1 month ago Counter.php 1 month ago Events.php 6 months ago FaqSchema.php 1 month ago FluidTypography.php 4 months ago Gallery.php 8 months ago GravityFormsInline.php 1 month ago Headline.php 1 month ago InsertPost.php 1 month ago ProductCategories.php 8 months ago ReadingProgress.php 7 months ago ReadingTime.php 8 months ago ShapeAnimations.php 1 month ago StackedImages.php 4 months ago StickyColumn.php 1 month ago Testimonials.php 8 months ago TextAnimation.php 1 month ago
ProductCategories.php
347 lines
1 <?php
2 /**
3 * Product Categories module for FrontBlocks.
4 *
5 * @package FrontBlocks
6 * @author Alex Castellón <castellon@close.technology>
7 * @copyright 2025 Closemarketing
8 * @version 1.0
9 */
10
11 namespace FrontBlocks\Frontend;
12
13 use WP_Block_Type_Registry;
14
15 defined( 'ABSPATH' ) || exit;
16
17 /**
18 * ProductCategories class.
19 *
20 * @since 1.0.0
21 */
22 class ProductCategories {
23
24 /**
25 * Constructor.
26 */
27 public function __construct() {
28 add_action( 'init', array( $this, 'register_product_categories_block' ), 20 );
29 add_action( 'init', array( $this, 'enable_rest_api_for_product_cat' ), 20 );
30 add_action( 'enqueue_block_editor_assets', array( $this, 'enqueue_block_editor_assets' ) );
31 add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_frontend_styles' ) );
32 add_action( 'rest_api_init', array( $this, 'register_rest_fields' ) );
33 }
34
35 /**
36 * Enable REST API for product_cat taxonomy.
37 *
38 * @return void
39 */
40 public function enable_rest_api_for_product_cat() {
41 global $wp_taxonomies;
42
43 if ( isset( $wp_taxonomies['product_cat'] ) ) {
44 $wp_taxonomies['product_cat']->show_in_rest = true;
45 $wp_taxonomies['product_cat']->rest_base = 'product_cat';
46 $wp_taxonomies['product_cat']->rest_controller_class = 'WP_REST_Terms_Controller';
47 }
48 }
49
50 /**
51 * Register custom REST API fields for product_cat taxonomy.
52 *
53 * @return void
54 */
55 public function register_rest_fields() {
56 register_rest_field(
57 'product_cat',
58 'category_image',
59 array(
60 'get_callback' => array( $this, 'get_category_image' ),
61 'schema' => array(
62 'description' => __( 'Category thumbnail image.', 'frontblocks' ),
63 'type' => 'object',
64 ),
65 )
66 );
67 }
68
69 /**
70 * Get category image for REST API.
71 *
72 * @param array $term_data Category term data.
73 * @return array|null Image data.
74 */
75 public function get_category_image( $term_data ) {
76 $thumbnail_id = get_term_meta( $term_data['id'], 'thumbnail_id', true );
77
78 if ( $thumbnail_id ) {
79 $image_url = wp_get_attachment_image_url( $thumbnail_id, 'woocommerce_thumbnail' );
80 if ( $image_url ) {
81 return array(
82 'src' => $image_url,
83 'id' => $thumbnail_id,
84 );
85 }
86 }
87
88 if ( function_exists( 'wc_placeholder_img_src' ) ) {
89 return array(
90 'src' => wc_placeholder_img_src(),
91 'id' => 0,
92 );
93 }
94
95 return null;
96 }
97
98 /**
99 * Enqueue frontend styles for the grid.
100 * Usa la constante FRBL_PLUGIN_URL para construir la URL pública correcta.
101 *
102 * @return void
103 */
104 public function enqueue_frontend_styles() {
105 wp_register_style(
106 'frontblocks-product-categories-grid-style',
107 FRBL_PLUGIN_URL . 'assets/product-categories/frontblocks-product-categories.css',
108 array(),
109 FRBL_VERSION
110 );
111
112 if ( is_admin() || has_block( 'frontblocks/product-categories' ) ) {
113 wp_enqueue_style( 'frontblocks-product-categories-grid-style' );
114 }
115 }
116
117 /**
118 * Enqueue block editor assets.
119 *
120 * @return void
121 */
122 public function enqueue_block_editor_assets() {
123 // Enqueue styles for editor.
124 wp_enqueue_style(
125 'frontblocks-product-categories-grid-style',
126 FRBL_PLUGIN_URL . 'assets/product-categories/frontblocks-product-categories.css',
127 array(),
128 FRBL_VERSION
129 );
130
131 wp_enqueue_script(
132 'frontblocks-product-categories-option',
133 FRBL_PLUGIN_URL . 'assets/product-categories/frontblocks-product-categories.js',
134 array( 'wp-blocks', 'wp-element', 'wp-components', 'wp-data', 'wp-editor', 'wp-api-fetch', 'wp-i18n' ),
135 FRBL_VERSION,
136 true
137 );
138
139 // Set script translations for JavaScript.
140 wp_set_script_translations(
141 'frontblocks-product-categories-option',
142 'frontblocks'
143 );
144
145 wp_localize_script(
146 'frontblocks-product-categories-option',
147 'frblProductCategories',
148 array(
149 'nonce' => wp_create_nonce( 'wp_rest' ),
150 )
151 );
152 }
153
154 /**
155 * Register the Product Categories block.
156 *
157 * @return void
158 */
159 public function register_product_categories_block() {
160 if ( ! class_exists( 'WooCommerce' ) ) {
161 return;
162 }
163
164 $args = array(
165 'editor_script' => 'frontblocks-product-categories-option',
166 'render_callback' => array( $this, 'render_product_categories_block' ),
167 'attributes' => array(
168 'count' => array(
169 'type' => 'number',
170 'default' => 5,
171 ),
172 'orderby' => array(
173 'type' => 'string',
174 'default' => 'count',
175 ),
176 'order' => array(
177 'type' => 'string',
178 'default' => 'DESC',
179 ),
180 'hideEmpty' => array(
181 'type' => 'boolean',
182 'default' => false,
183 ),
184 'showCount' => array(
185 'type' => 'boolean',
186 'default' => true,
187 ),
188 'className' => array(
189 'type' => 'string',
190 'default' => '',
191 ),
192 'columns' => array(
193 'type' => 'number',
194 'default' => 2,
195 ),
196 'bgColor' => array(
197 'type' => 'string',
198 'default' => 'rgba(255, 255, 255, 0.5)',
199 ),
200 'borderColor' => array(
201 'type' => 'string',
202 'default' => '#dddddd',
203 ),
204 'borderWidth' => array(
205 'type' => 'number',
206 'default' => 1,
207 ),
208 'borderRadius' => array(
209 'type' => 'number',
210 'default' => 20,
211 ),
212 'textColor' => array(
213 'type' => 'string',
214 'default' => 'inherit',
215 ),
216 'hoverBgColor' => array(
217 'type' => 'string',
218 'default' => 'rgba(255, 255, 255, 0.7)',
219 ),
220 'hoverBorderColor' => array(
221 'type' => 'string',
222 'default' => '#555555',
223 ),
224 'hoverTextColor' => array(
225 'type' => 'string',
226 'default' => 'inherit',
227 ),
228 ),
229 );
230
231 if ( ! WP_Block_Type_Registry::get_instance()->is_registered( 'frontblocks/product-categories' ) ) {
232 register_block_type(
233 'frontblocks/product-categories',
234 $args
235 );
236 }
237 }
238
239 /**
240 * Render the Product Categories block on frontend.
241 *
242 * @param array $attributes Block attributes.
243 * @return string HTML output.
244 */
245 public function render_product_categories_block( $attributes ) {
246 if ( ! class_exists( 'WooCommerce' ) ) {
247 return '';
248 }
249
250 $count = absint( $attributes['count'] ?? 5 );
251 $orderby = sanitize_key( $attributes['orderby'] ?? 'count' );
252 $order = strtoupper( sanitize_key( $attributes['order'] ?? 'DESC' ) );
253 $hide_empty = $attributes['hideEmpty'] ?? false;
254 $show_count = $attributes['showCount'] ?? true;
255 $columns = absint( $attributes['columns'] ?? 2 );
256
257 $bg_color = sanitize_text_field( $attributes['bgColor'] ?? 'rgba(255, 255, 255, 0.5)' );
258 $border_color = sanitize_text_field( $attributes['borderColor'] ?? '#dddddd' );
259 $border_width = absint( $attributes['borderWidth'] ?? 1 );
260 $border_radius = absint( $attributes['borderRadius'] ?? 20 );
261 $text_color = sanitize_text_field( $attributes['textColor'] ?? 'inherit' );
262 $hover_bg_color = sanitize_text_field( $attributes['hoverBgColor'] ?? 'rgba(255, 255, 255, 0.7)' );
263 $hover_border_color = sanitize_text_field( $attributes['hoverBorderColor'] ?? '#555555' );
264 $hover_text_color = sanitize_text_field( $attributes['hoverTextColor'] ?? 'inherit' );
265
266 /**
267 * Lógica para "Mostrar todas"
268 * Si el 'count' es 999 (el valor máximo en el editor),
269 * pasamos 'number' => 0 a get_terms para indicar que no hay límite.
270 */
271 $query_limit = ( 999 === $count ) ? 0 : $count;
272
273 $args = array(
274 'taxonomy' => 'product_cat',
275 'orderby' => $orderby,
276 'order' => $order,
277 'number' => $query_limit,
278 'hide_empty' => (bool) $hide_empty,
279 );
280 $categories = get_terms( $args );
281
282 if ( is_wp_error( $categories ) || empty( $categories ) ) {
283 if ( current_user_can( 'manage_options' ) ) {
284 $msg = is_wp_error( $categories ) ? $categories->get_error_message() : 'ATENCIÓN: No se encontraron categorías de producto.';
285 return '<div style="padding: 10px; border: 1px solid orange; background: #ffe;">' . $msg . '</div>';
286 }
287 return '';
288 }
289
290 $wrapper_class = 'frbl-product-categories-grid';
291 if ( ! empty( $attributes['className'] ) ) {
292 $wrapper_class .= ' ' . esc_attr( $attributes['className'] );
293 }
294
295 $style_vars = sprintf(
296 '--frbl-grid-columns: %d; --frbl-bg-color: %s; --frbl-border-color: %s; --frbl-border-width: %dpx; --frbl-text-color: %s; --frbl-hover-bg-color: %s; --frbl-hover-border-color: %s; --frbl-hover-text-color: %s; --frbl-border-radius: %dpx;',
297 $columns,
298 $bg_color,
299 $border_color,
300 $border_width,
301 $text_color,
302 $hover_bg_color,
303 $hover_border_color,
304 $hover_text_color,
305 $border_radius
306 );
307
308 ob_start();
309 ?>
310 <div class="<?php echo esc_attr( $wrapper_class ); ?>" style="<?php echo esc_attr( $style_vars ); ?>">
311 <?php
312 foreach ( $categories as $category ) :
313 $thumbnail_id = get_term_meta( $category->term_id, 'thumbnail_id', true );
314
315 if ( $thumbnail_id ) {
316 $image_url = wp_get_attachment_image_url( $thumbnail_id, 'woocommerce_thumbnail' );
317 } elseif ( function_exists( 'wc_placeholder_img_src' ) ) {
318 $image_url = wc_placeholder_img_src();
319 } else {
320 $image_url = 'https://placehold.co/600x400/eeeeee/333333?text=Product+Category';
321 }
322
323 $link = esc_url( get_term_link( $category, 'product_cat' ) );
324 ?>
325 <div class="frbl-category-item frbl-category-<?php echo esc_attr( $category->slug ); ?>">
326 <a href="<?php echo esc_url( $link ); ?>" class="frbl-category-link">
327 <div class="frbl-category-image-wrap">
328 <img
329 src="<?php echo esc_url( $image_url ); ?>"
330 alt="<?php echo esc_attr( $category->name ); ?>"
331 class="frbl-category-image"
332 />
333 </div>
334 <h3 class="frbl-category-name">
335 <?php echo esc_html( $category->name ); ?><?php echo $show_count ? ' (' . esc_html( $category->count ) . ')' : ''; ?>
336 </h3>
337 </a>
338 </div>
339 <?php endforeach; ?>
340 </div>
341 <?php
342 return ob_get_clean();
343 }
344 }
345
346 new ProductCategories();
347