| 1 |
<?php |
| 2 |
/** |
| 3 |
* Plugin Name: Blockenberg |
| 4 |
* Description: Advanced Gutenberg Blocks for WordPress Block Editor |
| 5 |
* Version: 2.0.5 |
| 6 |
* Author: Blockenberg |
| 7 |
* Text Domain: blockenberg |
| 8 |
* Domain Path: /languages |
| 9 |
* License: GPLv2 or later |
| 10 |
* License URI: https://www.gnu.org/licenses/gpl-2.0.html |
| 11 |
*/ |
| 12 |
|
| 13 |
defined( 'ABSPATH' ) || exit; |
| 14 |
|
| 15 |
/** |
| 16 |
* Enqueue common editor styles and scripts for all Blockenberg blocks |
| 17 |
*/ |
| 18 |
add_action( 'enqueue_block_editor_assets', function() { |
| 19 |
wp_enqueue_style( 'dashicons' ); |
| 20 |
// Ensure Media Library modal assets are available for blocks using MediaUpload. |
| 21 |
wp_enqueue_media(); |
| 22 |
|
| 23 |
// Common editor scripts (branded icons) |
| 24 |
$editor_js = __DIR__ . '/assets/js/editor.js'; |
| 25 |
if ( file_exists( $editor_js ) ) { |
| 26 |
wp_enqueue_script( |
| 27 |
'bkbg-editor-common', |
| 28 |
plugins_url( 'assets/js/editor.js', __FILE__ ), |
| 29 |
array( 'wp-blocks', 'wp-dom-ready', 'wp-element' ), |
| 30 |
filemtime( $editor_js ), |
| 31 |
true |
| 32 |
); |
| 33 |
} |
| 34 |
|
| 35 |
// Inspector tabs (General / Advanced) for all Blockenberg blocks |
| 36 |
$inspector_tabs_js = __DIR__ . '/assets/js/inspector-tabs.js'; |
| 37 |
if ( file_exists( $inspector_tabs_js ) ) { |
| 38 |
wp_enqueue_script( |
| 39 |
'bkbg-inspector-tabs', |
| 40 |
plugins_url( 'assets/js/inspector-tabs.js', __FILE__ ), |
| 41 |
array( 'wp-blocks', 'wp-element', 'wp-compose', 'wp-hooks', 'wp-block-editor', 'wp-components', 'wp-i18n', 'wp-data' ), |
| 42 |
filemtime( $inspector_tabs_js ), |
| 43 |
true |
| 44 |
); |
| 45 |
} |
| 46 |
}); |
| 47 |
|
| 48 |
/** |
| 49 |
* Enqueue editor styles in a way compatible with the iframe-based editor canvas. |
| 50 |
*/ |
| 51 |
add_action( 'enqueue_block_assets', function () { |
| 52 |
// Avoid loading editor-only CSS on the frontend. |
| 53 |
if ( ! is_admin() ) { |
| 54 |
return; |
| 55 |
} |
| 56 |
|
| 57 |
// Layout system CSS (shared variables and utilities) |
| 58 |
$layout_css = __DIR__ . '/assets/css/layout.css'; |
| 59 |
if ( file_exists( $layout_css ) ) { |
| 60 |
wp_enqueue_style( |
| 61 |
'bkbg-layout-system', |
| 62 |
plugins_url( 'assets/css/layout.css', __FILE__ ), |
| 63 |
array(), |
| 64 |
filemtime( $layout_css ) |
| 65 |
); |
| 66 |
} |
| 67 |
|
| 68 |
// Common editor styles for all blocks |
| 69 |
$editor_css = __DIR__ . '/assets/css/editor.css'; |
| 70 |
if ( file_exists( $editor_css ) ) { |
| 71 |
wp_enqueue_style( |
| 72 |
'bkbg-editor-common', |
| 73 |
plugins_url( 'assets/css/editor.css', __FILE__ ), |
| 74 |
array( 'bkbg-layout-system' ), |
| 75 |
filemtime( $editor_css ) |
| 76 |
); |
| 77 |
} |
| 78 |
} ); |
| 79 |
|
| 80 |
/** |
| 81 |
* Register block scripts with dependencies (but don't enqueue them yet) |
| 82 |
*/ |
| 83 |
add_action( 'init', function () { |
| 84 |
$blocks_dir = __DIR__ . '/blocks/'; |
| 85 |
|
| 86 |
// Standard WordPress script dependencies for blocks |
| 87 |
$script_dependencies = array( |
| 88 |
'wp-blocks', |
| 89 |
'wp-element', |
| 90 |
'wp-i18n', |
| 91 |
'wp-block-editor', |
| 92 |
'wp-components', |
| 93 |
'wp-dom-ready', |
| 94 |
'wp-data' |
| 95 |
); |
| 96 |
|
| 97 |
// Standard WordPress style dependencies for blocks |
| 98 |
$style_dependencies = array( |
| 99 |
'dashicons' |
| 100 |
); |
| 101 |
|
| 102 |
// Register layout system CSS for frontend |
| 103 |
$layout_css = __DIR__ . '/assets/css/layout.css'; |
| 104 |
if ( file_exists( $layout_css ) ) { |
| 105 |
wp_register_style( |
| 106 |
'bkbg-layout-system', |
| 107 |
plugins_url( 'assets/css/layout.css', __FILE__ ), |
| 108 |
array(), |
| 109 |
filemtime( $layout_css ) |
| 110 |
); |
| 111 |
} |
| 112 |
|
| 113 |
// Automatically register scripts for all blocks |
| 114 |
foreach ( glob( $blocks_dir . '*', GLOB_ONLYDIR ) as $block_dir ) { |
| 115 |
$block_name = basename( $block_dir ); |
| 116 |
$script_file = $block_dir . '/index.js'; |
| 117 |
|
| 118 |
$style_file = $block_dir . '/style.css'; |
| 119 |
$frontend_file = $block_dir . '/frontend.js'; |
| 120 |
|
| 121 |
// Check if block has a JavaScript file |
| 122 |
if ( file_exists( $script_file ) ) { |
| 123 |
wp_register_script( |
| 124 |
'bkbg-' . $block_name . '-editor', |
| 125 |
plugins_url( 'blocks/' . $block_name . '/index.js', __FILE__ ), |
| 126 |
$script_dependencies, |
| 127 |
filemtime( $script_file ), |
| 128 |
true |
| 129 |
); |
| 130 |
} |
| 131 |
|
| 132 |
// Check if block has a CSS file |
| 133 |
if ( file_exists( $style_file ) ) { |
| 134 |
// Layout blocks need the layout system CSS |
| 135 |
$block_style_deps = $style_dependencies; |
| 136 |
if ( in_array( $block_name, array( 'section', 'row', 'column' ), true ) ) { |
| 137 |
$block_style_deps[] = 'bkbg-layout-system'; |
| 138 |
} |
| 139 |
|
| 140 |
wp_register_style( |
| 141 |
'bkbg-' . $block_name . '-style', |
| 142 |
plugins_url( 'blocks/' . $block_name . '/style.css', __FILE__ ), |
| 143 |
$block_style_deps, |
| 144 |
filemtime( $style_file ) |
| 145 |
); |
| 146 |
} |
| 147 |
|
| 148 |
// Check if block has a frontend JavaScript file |
| 149 |
if ( file_exists( $frontend_file ) ) { |
| 150 |
wp_register_script( |
| 151 |
'bkbg-' . $block_name . '-frontend', |
| 152 |
plugins_url( 'blocks/' . $block_name . '/frontend.js', __FILE__ ), |
| 153 |
array(), |
| 154 |
filemtime( $frontend_file ), |
| 155 |
true |
| 156 |
); |
| 157 |
} |
| 158 |
} |
| 159 |
|
| 160 |
// Automatically register all blocks in the /blocks directory |
| 161 |
foreach ( glob( __DIR__ . '/blocks/*/block.json' ) as $metadata ) { |
| 162 |
register_block_type( dirname( $metadata ) ); |
| 163 |
} |
| 164 |
} ); |
| 165 |
|
| 166 |
// Register custom block category and ensure Blockenberg blocks appear in it. |
| 167 |
add_filter( 'block_categories_all', function( $categories, $block_editor_context ) { |
| 168 |
// Prepend our custom category so it appears first. |
| 169 |
array_unshift( $categories, array( |
| 170 |
'slug' => 'blockenberg', |
| 171 |
'title' => __( 'Blockenberg Blocks', 'blockenberg' ), |
| 172 |
'icon' => null, |
| 173 |
) ); |
| 174 |
return $categories; |
| 175 |
}, 10, 2 ); |
| 176 |
|
| 177 |
/** |
| 178 |
* Register advanced layout attributes on the SERVER side for every Blockenberg block. |
| 179 |
* Without this, WordPress strips unknown attributes during server-side parsing |
| 180 |
* (array_intersect_key in WP_Block_Type::prepare_attributes_for_render). |
| 181 |
*/ |
| 182 |
add_filter( 'register_block_type_args', function ( $args, $block_type ) { |
| 183 |
if ( strpos( $block_type, 'blockenberg/' ) !== 0 ) { |
| 184 |
return $args; |
| 185 |
} |
| 186 |
|
| 187 |
$sides = array( 'Top', 'Right', 'Bottom', 'Left' ); |
| 188 |
$devices = array( '', 'Tablet', 'Mobile' ); |
| 189 |
$extra = array(); |
| 190 |
|
| 191 |
foreach ( array( 'bkbgMargin', 'bkbgPadding' ) as $prefix ) { |
| 192 |
foreach ( $sides as $side ) { |
| 193 |
foreach ( $devices as $device ) { |
| 194 |
$key = $prefix . $side . $device; |
| 195 |
$extra[ $key ] = array( 'type' => 'string', 'default' => '' ); |
| 196 |
$extra[ $key . 'Unit' ] = array( 'type' => 'string', 'default' => 'px' ); |
| 197 |
} |
| 198 |
} |
| 199 |
foreach ( $devices as $device ) { |
| 200 |
$extra[ $prefix . 'Linked' . $device ] = array( 'type' => 'boolean', 'default' => true ); |
| 201 |
} |
| 202 |
} |
| 203 |
|
| 204 |
foreach ( $devices as $device ) { |
| 205 |
$extra[ 'bkbgZIndex' . $device ] = array( 'type' => 'string', 'default' => '' ); |
| 206 |
} |
| 207 |
|
| 208 |
$extra['bkbgCssId'] = array( 'type' => 'string', 'default' => '' ); |
| 209 |
$extra['bkbgCssClasses'] = array( 'type' => 'string', 'default' => '' ); |
| 210 |
|
| 211 |
// ── Background attributes ── |
| 212 |
$extra['bkbgBgType'] = array( 'type' => 'string', 'default' => '' ); |
| 213 |
$extra['bkbgBgHoverType'] = array( 'type' => 'string', 'default' => '' ); |
| 214 |
$extra['bkbgBgColor'] = array( 'type' => 'string', 'default' => '' ); |
| 215 |
$extra['bkbgBgHoverColor']= array( 'type' => 'string', 'default' => '' ); |
| 216 |
|
| 217 |
// Classic image (responsive) — normal & hover |
| 218 |
foreach ( array( 'bkbgBgImage', 'bkbgBgHoverImage' ) as $img_prefix ) { |
| 219 |
foreach ( $devices as $device ) { |
| 220 |
$extra[ $img_prefix . $device ] = array( 'type' => 'string', 'default' => '' ); |
| 221 |
$extra[ $img_prefix . 'Id' . $device ] = array( 'type' => 'number', 'default' => 0 ); |
| 222 |
} |
| 223 |
} |
| 224 |
|
| 225 |
// Classic image settings — normal & hover (position/repeat/size responsive, attachment global) |
| 226 |
foreach ( array( 'bkbgBg', 'bkbgBgHover' ) as $s_prefix ) { |
| 227 |
foreach ( $devices as $device ) { |
| 228 |
$extra[ $s_prefix . 'Position' . $device ] = array( 'type' => 'string', 'default' => '' ); |
| 229 |
$extra[ $s_prefix . 'PositionCustomX' . $device ] = array( 'type' => 'string', 'default' => '' ); |
| 230 |
$extra[ $s_prefix . 'PositionCustomY' . $device ] = array( 'type' => 'string', 'default' => '' ); |
| 231 |
$extra[ $s_prefix . 'Repeat' . $device ] = array( 'type' => 'string', 'default' => '' ); |
| 232 |
$extra[ $s_prefix . 'Size' . $device ] = array( 'type' => 'string', 'default' => '' ); |
| 233 |
$extra[ $s_prefix . 'SizeCustomW' . $device ] = array( 'type' => 'string', 'default' => '' ); |
| 234 |
$extra[ $s_prefix . 'SizeCustomH' . $device ] = array( 'type' => 'string', 'default' => '' ); |
| 235 |
} |
| 236 |
$extra[ $s_prefix . 'Attachment' ] = array( 'type' => 'string', 'default' => '' ); |
| 237 |
} |
| 238 |
|
| 239 |
// Gradient — normal |
| 240 |
$extra['bkbgBgGradColor1'] = array( 'type' => 'string', 'default' => '' ); |
| 241 |
$extra['bkbgBgGradColor2'] = array( 'type' => 'string', 'default' => '' ); |
| 242 |
$extra['bkbgBgGradType'] = array( 'type' => 'string', 'default' => 'linear' ); |
| 243 |
foreach ( $devices as $device ) { |
| 244 |
$extra[ 'bkbgBgGradLoc1' . $device ] = array( 'type' => 'string', 'default' => '' ); |
| 245 |
$extra[ 'bkbgBgGradLoc2' . $device ] = array( 'type' => 'string', 'default' => '' ); |
| 246 |
$extra[ 'bkbgBgGradAngle' . $device ] = array( 'type' => 'string', 'default' => '' ); |
| 247 |
$extra[ 'bkbgBgGradPosition' . $device ] = array( 'type' => 'string', 'default' => '' ); |
| 248 |
} |
| 249 |
|
| 250 |
// Gradient — hover |
| 251 |
$extra['bkbgBgHoverGradColor1'] = array( 'type' => 'string', 'default' => '' ); |
| 252 |
$extra['bkbgBgHoverGradColor2'] = array( 'type' => 'string', 'default' => '' ); |
| 253 |
$extra['bkbgBgHoverGradType'] = array( 'type' => 'string', 'default' => 'linear' ); |
| 254 |
foreach ( $devices as $device ) { |
| 255 |
$extra[ 'bkbgBgHoverGradLoc1' . $device ] = array( 'type' => 'string', 'default' => '' ); |
| 256 |
$extra[ 'bkbgBgHoverGradLoc2' . $device ] = array( 'type' => 'string', 'default' => '' ); |
| 257 |
$extra[ 'bkbgBgHoverGradAngle' . $device ] = array( 'type' => 'string', 'default' => '' ); |
| 258 |
$extra[ 'bkbgBgHoverGradPosition' . $device ] = array( 'type' => 'string', 'default' => '' ); |
| 259 |
} |
| 260 |
|
| 261 |
|
| 262 |
|
| 263 |
// ── Border attributes ── |
| 264 |
|
| 265 |
// Border Type (normal & hover) |
| 266 |
$extra['bkbgBorderType'] = array( 'type' => 'string', 'default' => '' ); |
| 267 |
$extra['bkbgBorderHoverType'] = array( 'type' => 'string', 'default' => '' ); |
| 268 |
|
| 269 |
// Border Width — per side, per device, with unit (normal & hover) |
| 270 |
foreach ( array( 'bkbgBorderWidth', 'bkbgBorderHoverWidth' ) as $prefix ) { |
| 271 |
foreach ( $sides as $side ) { |
| 272 |
foreach ( $devices as $device ) { |
| 273 |
$key = $prefix . $side . $device; |
| 274 |
$extra[ $key ] = array( 'type' => 'string', 'default' => '' ); |
| 275 |
$extra[ $key . 'Unit' ] = array( 'type' => 'string', 'default' => 'px' ); |
| 276 |
} |
| 277 |
} |
| 278 |
foreach ( $devices as $device ) { |
| 279 |
$extra[ $prefix . 'Linked' . $device ] = array( 'type' => 'boolean', 'default' => true ); |
| 280 |
} |
| 281 |
} |
| 282 |
|
| 283 |
// Border Color (normal & hover) |
| 284 |
$extra['bkbgBorderColor'] = array( 'type' => 'string', 'default' => '' ); |
| 285 |
$extra['bkbgBorderHoverColor'] = array( 'type' => 'string', 'default' => '' ); |
| 286 |
|
| 287 |
// Border Radius — per corner, per device, with unit (normal & hover) |
| 288 |
foreach ( array( 'bkbgBorderRadius', 'bkbgBorderHoverRadius' ) as $prefix ) { |
| 289 |
foreach ( $sides as $side ) { |
| 290 |
foreach ( $devices as $device ) { |
| 291 |
$key = $prefix . $side . $device; |
| 292 |
$extra[ $key ] = array( 'type' => 'string', 'default' => '' ); |
| 293 |
$extra[ $key . 'Unit' ] = array( 'type' => 'string', 'default' => 'px' ); |
| 294 |
} |
| 295 |
} |
| 296 |
foreach ( $devices as $device ) { |
| 297 |
$extra[ $prefix . 'Linked' . $device ] = array( 'type' => 'boolean', 'default' => true ); |
| 298 |
} |
| 299 |
} |
| 300 |
|
| 301 |
// Box Shadow (normal) |
| 302 |
$extra['bkbgShadowColor'] = array( 'type' => 'string', 'default' => '' ); |
| 303 |
$extra['bkbgShadowH'] = array( 'type' => 'string', 'default' => '' ); |
| 304 |
$extra['bkbgShadowV'] = array( 'type' => 'string', 'default' => '' ); |
| 305 |
$extra['bkbgShadowBlur'] = array( 'type' => 'string', 'default' => '' ); |
| 306 |
$extra['bkbgShadowSpread'] = array( 'type' => 'string', 'default' => '' ); |
| 307 |
$extra['bkbgShadowPosition'] = array( 'type' => 'string', 'default' => '' ); |
| 308 |
|
| 309 |
// Box Shadow (hover) |
| 310 |
$extra['bkbgShadowHoverColor'] = array( 'type' => 'string', 'default' => '' ); |
| 311 |
$extra['bkbgShadowHoverH'] = array( 'type' => 'string', 'default' => '' ); |
| 312 |
$extra['bkbgShadowHoverV'] = array( 'type' => 'string', 'default' => '' ); |
| 313 |
$extra['bkbgShadowHoverBlur'] = array( 'type' => 'string', 'default' => '' ); |
| 314 |
$extra['bkbgShadowHoverSpread'] = array( 'type' => 'string', 'default' => '' ); |
| 315 |
$extra['bkbgShadowHoverPosition'] = array( 'type' => 'string', 'default' => '' ); |
| 316 |
|
| 317 |
|
| 318 |
// Responsive visibility |
| 319 |
$extra['bkbgHideDesktop'] = array( 'type' => 'boolean', 'default' => false ); |
| 320 |
$extra['bkbgHideTablet'] = array( 'type' => 'boolean', 'default' => false ); |
| 321 |
$extra['bkbgHideMobile'] = array( 'type' => 'boolean', 'default' => false ); |
| 322 |
|
| 323 |
if ( ! isset( $args['attributes'] ) || ! is_array( $args['attributes'] ) ) { |
| 324 |
$args['attributes'] = array(); |
| 325 |
} |
| 326 |
$args['attributes'] = array_merge( $args['attributes'], $extra ); |
| 327 |
|
| 328 |
return $args; |
| 329 |
}, 10, 2 ); |
| 330 |
|
| 331 |
/** |
| 332 |
* Render ALL advanced layout CSS (desktop + tablet + mobile) for Blockenberg blocks. |
| 333 |
* Desktop styles are output as inline styles on the wrapper. |
| 334 |
* Responsive styles use <style> tags with media queries. |
| 335 |
* CSS ID / CSS Classes are also injected here for reliability. |
| 336 |
*/ |
| 337 |
add_filter( 'render_block', function ( $block_content, $block ) { |
| 338 |
if ( empty( $block['blockName'] ) || strpos( $block['blockName'], 'blockenberg/' ) !== 0 ) { |
| 339 |
return $block_content; |
| 340 |
} |
| 341 |
|
| 342 |
$attrs = $block['attrs'] ?? array(); |
| 343 |
if ( empty( $attrs ) ) { |
| 344 |
return $block_content; |
| 345 |
} |
| 346 |
|
| 347 |
$sides = array( 'Top', 'Right', 'Bottom', 'Left' ); |
| 348 |
$devices = array( 'desktop', 'tablet', 'mobile' ); |
| 349 |
|
| 350 |
// Collect CSS rules per device |
| 351 |
$css = array( 'desktop' => array(), 'tablet' => array(), 'mobile' => array() ); |
| 352 |
|
| 353 |
foreach ( array( 'bkbgMargin' => 'margin', 'bkbgPadding' => 'padding' ) as $prefix => $prop ) { |
| 354 |
foreach ( $sides as $side ) { |
| 355 |
foreach ( $devices as $dev ) { |
| 356 |
$suffix = 'desktop' === $dev ? '' : ( 'tablet' === $dev ? 'Tablet' : 'Mobile' ); |
| 357 |
$val = isset( $attrs[ $prefix . $side . $suffix ] ) ? $attrs[ $prefix . $side . $suffix ] : ''; |
| 358 |
$unit = isset( $attrs[ $prefix . $side . $suffix . 'Unit' ] ) ? $attrs[ $prefix . $side . $suffix . 'Unit' ] : 'px'; |
| 359 |
if ( '' !== $val && '' !== trim( (string) $val ) ) { |
| 360 |
$css[ $dev ][] = $prop . '-' . strtolower( $side ) . ':' . $val . $unit . ' !important'; |
| 361 |
} |
| 362 |
} |
| 363 |
} |
| 364 |
} |
| 365 |
|
| 366 |
// Z-Index per device |
| 367 |
foreach ( $devices as $dev ) { |
| 368 |
$suffix = 'desktop' === $dev ? '' : ( 'tablet' === $dev ? 'Tablet' : 'Mobile' ); |
| 369 |
$zi = isset( $attrs[ 'bkbgZIndex' . $suffix ] ) ? $attrs[ 'bkbgZIndex' . $suffix ] : ''; |
| 370 |
if ( '' !== $zi && '' !== trim( (string) $zi ) ) { |
| 371 |
$css[ $dev ][] = 'z-index:' . intval( $zi ); |
| 372 |
$css[ $dev ][] = 'position:relative'; |
| 373 |
} |
| 374 |
} |
| 375 |
|
| 376 |
// ── Background CSS ── |
| 377 |
$hover_css = array( 'desktop' => array(), 'tablet' => array(), 'mobile' => array() ); |
| 378 |
|
| 379 |
$bg_type = ! empty( $attrs['bkbgBgType'] ) ? $attrs['bkbgBgType'] : ''; |
| 380 |
$hover_type = ! empty( $attrs['bkbgBgHoverType'] ) ? $attrs['bkbgBgHoverType'] : ''; |
| 381 |
|
| 382 |
// Helper: build gradient value |
| 383 |
$build_gradient = function ( $attrs, $prefix, $suffix ) { |
| 384 |
$c1 = ! empty( $attrs[ $prefix . 'GradColor1' ] ) ? $attrs[ $prefix . 'GradColor1' ] : ''; |
| 385 |
$c2 = ! empty( $attrs[ $prefix . 'GradColor2' ] ) ? $attrs[ $prefix . 'GradColor2' ] : ''; |
| 386 |
if ( '' === $c1 && '' === $c2 ) return ''; |
| 387 |
if ( '' === $c1 ) $c1 = 'transparent'; |
| 388 |
if ( '' === $c2 ) $c2 = 'transparent'; |
| 389 |
|
| 390 |
// Location 1 — with fallback to desktop |
| 391 |
$loc1 = ''; |
| 392 |
if ( '' !== $suffix && ! empty( $attrs[ $prefix . 'GradLoc1' . $suffix ] ) ) { |
| 393 |
$loc1 = $attrs[ $prefix . 'GradLoc1' . $suffix ]; |
| 394 |
} elseif ( ! empty( $attrs[ $prefix . 'GradLoc1' ] ) ) { |
| 395 |
$loc1 = $attrs[ $prefix . 'GradLoc1' ]; |
| 396 |
} |
| 397 |
|
| 398 |
// Location 2 |
| 399 |
$loc2 = ''; |
| 400 |
if ( '' !== $suffix && ! empty( $attrs[ $prefix . 'GradLoc2' . $suffix ] ) ) { |
| 401 |
$loc2 = $attrs[ $prefix . 'GradLoc2' . $suffix ]; |
| 402 |
} elseif ( ! empty( $attrs[ $prefix . 'GradLoc2' ] ) ) { |
| 403 |
$loc2 = $attrs[ $prefix . 'GradLoc2' ]; |
| 404 |
} |
| 405 |
|
| 406 |
// Angle |
| 407 |
$angle = ''; |
| 408 |
if ( '' !== $suffix && isset( $attrs[ $prefix . 'GradAngle' . $suffix ] ) && '' !== $attrs[ $prefix . 'GradAngle' . $suffix ] ) { |
| 409 |
$angle = $attrs[ $prefix . 'GradAngle' . $suffix ]; |
| 410 |
} elseif ( isset( $attrs[ $prefix . 'GradAngle' ] ) && '' !== $attrs[ $prefix . 'GradAngle' ] ) { |
| 411 |
$angle = $attrs[ $prefix . 'GradAngle' ]; |
| 412 |
} |
| 413 |
|
| 414 |
$type = ! empty( $attrs[ $prefix . 'GradType' ] ) ? $attrs[ $prefix . 'GradType' ] : 'linear'; |
| 415 |
|
| 416 |
$stop1 = esc_attr( $c1 ) . ( '' !== $loc1 ? ' ' . intval( $loc1 ) . '%' : '' ); |
| 417 |
$stop2 = esc_attr( $c2 ) . ( '' !== $loc2 ? ' ' . intval( $loc2 ) . '%' : '' ); |
| 418 |
|
| 419 |
if ( 'radial' === $type ) { |
| 420 |
// Radial position |
| 421 |
$pos = ''; |
| 422 |
if ( '' !== $suffix && ! empty( $attrs[ $prefix . 'GradPosition' . $suffix ] ) ) { |
| 423 |
$pos = $attrs[ $prefix . 'GradPosition' . $suffix ]; |
| 424 |
} elseif ( ! empty( $attrs[ $prefix . 'GradPosition' ] ) ) { |
| 425 |
$pos = $attrs[ $prefix . 'GradPosition' ]; |
| 426 |
} |
| 427 |
$at_part = '' !== $pos ? ' at ' . esc_attr( $pos ) : ''; |
| 428 |
return 'radial-gradient(circle' . $at_part . ',' . $stop1 . ',' . $stop2 . ')'; |
| 429 |
} |
| 430 |
|
| 431 |
$angle_part = '' !== $angle ? intval( $angle ) . 'deg,' : ''; |
| 432 |
return 'linear-gradient(' . $angle_part . $stop1 . ',' . $stop2 . ')'; |
| 433 |
}; |
| 434 |
|
| 435 |
// Helper: resolve responsive classic image settings (with desktop fallback) |
| 436 |
$get_img_setting = function ( $attrs, $prefix, $prop, $suffix ) { |
| 437 |
$val = ! empty( $attrs[ $prefix . $prop . $suffix ] ) ? $attrs[ $prefix . $prop . $suffix ] : ''; |
| 438 |
if ( '' === $val && '' !== $suffix ) { |
| 439 |
$val = ! empty( $attrs[ $prefix . $prop ] ) ? $attrs[ $prefix . $prop ] : ''; |
| 440 |
} |
| 441 |
return $val; |
| 442 |
}; |
| 443 |
|
| 444 |
$render_classic_image_css = function ( $attrs, $prefix, &$target_css, $devices ) use ( $get_img_setting ) { |
| 445 |
// Background color |
| 446 |
if ( ! empty( $attrs[ $prefix . 'Color' ] ) ) { |
| 447 |
$target_css['desktop'][] = 'background-color:' . esc_attr( $attrs[ $prefix . 'Color' ] ) . ' !important'; |
| 448 |
} |
| 449 |
// Attachment (global, not responsive) |
| 450 |
$attach = ! empty( $attrs[ $prefix . 'Attachment' ] ) ? esc_attr( $attrs[ $prefix . 'Attachment' ] ) : ''; |
| 451 |
if ( '' !== $attach ) { |
| 452 |
$target_css['desktop'][] = 'background-attachment:' . $attach . ' !important'; |
| 453 |
} |
| 454 |
|
| 455 |
foreach ( $devices as $dev ) { |
| 456 |
$suffix = 'desktop' === $dev ? '' : ( 'tablet' === $dev ? 'Tablet' : 'Mobile' ); |
| 457 |
$img = ! empty( $attrs[ $prefix . 'Image' . $suffix ] ) ? $attrs[ $prefix . 'Image' . $suffix ] : ''; |
| 458 |
if ( '' === $img && '' !== $suffix ) { |
| 459 |
$img = ! empty( $attrs[ $prefix . 'Image' ] ) ? $attrs[ $prefix . 'Image' ] : ''; |
| 460 |
} |
| 461 |
if ( '' !== $img ) { |
| 462 |
$target_css[ $dev ][] = 'background-image:url(' . esc_url( $img ) . ') !important'; |
| 463 |
|
| 464 |
// Position |
| 465 |
$pos = $get_img_setting( $attrs, $prefix, 'Position', $suffix ); |
| 466 |
if ( 'custom' === $pos ) { |
| 467 |
$cx = $get_img_setting( $attrs, $prefix, 'PositionCustomX', $suffix ); |
| 468 |
$cy = $get_img_setting( $attrs, $prefix, 'PositionCustomY', $suffix ); |
| 469 |
$cx = '' !== $cx ? intval( $cx ) . '%' : '50%'; |
| 470 |
$cy = '' !== $cy ? intval( $cy ) . '%' : '50%'; |
| 471 |
$target_css[ $dev ][] = 'background-position:' . $cx . ' ' . $cy . ' !important'; |
| 472 |
} elseif ( '' !== $pos ) { |
| 473 |
$target_css[ $dev ][] = 'background-position:' . esc_attr( $pos ) . ' !important'; |
| 474 |
} else { |
| 475 |
$target_css[ $dev ][] = 'background-position:center !important'; |
| 476 |
} |
| 477 |
|
| 478 |
// Repeat |
| 479 |
$repeat = $get_img_setting( $attrs, $prefix, 'Repeat', $suffix ); |
| 480 |
if ( '' !== $repeat ) { |
| 481 |
$target_css[ $dev ][] = 'background-repeat:' . esc_attr( $repeat ) . ' !important'; |
| 482 |
} |
| 483 |
|
| 484 |
// Size |
| 485 |
$size = $get_img_setting( $attrs, $prefix, 'Size', $suffix ); |
| 486 |
if ( 'custom' === $size ) { |
| 487 |
$sw = $get_img_setting( $attrs, $prefix, 'SizeCustomW', $suffix ); |
| 488 |
$sh = $get_img_setting( $attrs, $prefix, 'SizeCustomH', $suffix ); |
| 489 |
$sw = '' !== $sw ? intval( $sw ) . 'px' : 'auto'; |
| 490 |
$sh = '' !== $sh ? intval( $sh ) . 'px' : 'auto'; |
| 491 |
$target_css[ $dev ][] = 'background-size:' . $sw . ' ' . $sh . ' !important'; |
| 492 |
} elseif ( '' !== $size ) { |
| 493 |
$target_css[ $dev ][] = 'background-size:' . esc_attr( $size ) . ' !important'; |
| 494 |
} else { |
| 495 |
$target_css[ $dev ][] = 'background-size:cover !important'; |
| 496 |
} |
| 497 |
} |
| 498 |
} |
| 499 |
}; |
| 500 |
|
| 501 |
// Normal — Classic |
| 502 |
if ( 'classic' === $bg_type ) { |
| 503 |
$render_classic_image_css( $attrs, 'bkbgBg', $css, $devices ); |
| 504 |
} |
| 505 |
|
| 506 |
// Normal — Gradient |
| 507 |
if ( 'gradient' === $bg_type ) { |
| 508 |
foreach ( $devices as $dev ) { |
| 509 |
$suffix = 'desktop' === $dev ? '' : ( 'tablet' === $dev ? 'Tablet' : 'Mobile' ); |
| 510 |
$grad = $build_gradient( $attrs, 'bkbgBg', $suffix ); |
| 511 |
if ( '' !== $grad ) { |
| 512 |
$css[ $dev ][] = 'background-image:' . $grad . ' !important'; |
| 513 |
} |
| 514 |
} |
| 515 |
} |
| 516 |
|
| 517 |
// Hover — Classic |
| 518 |
if ( 'classic' === $hover_type ) { |
| 519 |
$render_classic_image_css( $attrs, 'bkbgBgHover', $hover_css, $devices ); |
| 520 |
} |
| 521 |
|
| 522 |
// Hover — Gradient |
| 523 |
if ( 'gradient' === $hover_type ) { |
| 524 |
foreach ( $devices as $dev ) { |
| 525 |
$suffix = 'desktop' === $dev ? '' : ( 'tablet' === $dev ? 'Tablet' : 'Mobile' ); |
| 526 |
$grad = $build_gradient( $attrs, 'bkbgBgHover', $suffix ); |
| 527 |
if ( '' !== $grad ) { |
| 528 |
$hover_css[ $dev ][] = 'background-image:' . $grad . ' !important'; |
| 529 |
} |
| 530 |
} |
| 531 |
} |
| 532 |
|
| 533 |
// ── Border CSS ── |
| 534 |
|
| 535 |
// Helper: build box-shadow value |
| 536 |
$build_shadow = function ( $attrs, $prefix ) { |
| 537 |
$h = isset( $attrs[ $prefix . 'H' ] ) && '' !== $attrs[ $prefix . 'H' ] ? intval( $attrs[ $prefix . 'H' ] ) : ''; |
| 538 |
$v = isset( $attrs[ $prefix . 'V' ] ) && '' !== $attrs[ $prefix . 'V' ] ? intval( $attrs[ $prefix . 'V' ] ) : ''; |
| 539 |
$blur = isset( $attrs[ $prefix . 'Blur' ] ) && '' !== $attrs[ $prefix . 'Blur' ] ? intval( $attrs[ $prefix . 'Blur' ] ) : ''; |
| 540 |
$spread = isset( $attrs[ $prefix . 'Spread' ] ) && '' !== $attrs[ $prefix . 'Spread' ] ? intval( $attrs[ $prefix . 'Spread' ] ) : ''; |
| 541 |
$color = ! empty( $attrs[ $prefix . 'Color' ] ) ? $attrs[ $prefix . 'Color' ] : ''; |
| 542 |
$pos = ! empty( $attrs[ $prefix . 'Position' ] ) ? $attrs[ $prefix . 'Position' ] : ''; |
| 543 |
|
| 544 |
if ( '' === $h && '' === $v && '' === $blur && '' === $spread && '' === $color ) return ''; |
| 545 |
|
| 546 |
$h = '' !== $h ? $h . 'px' : '0px'; |
| 547 |
$v = '' !== $v ? $v . 'px' : '0px'; |
| 548 |
$blur = '' !== $blur ? $blur . 'px' : '0px'; |
| 549 |
$spread = '' !== $spread ? $spread . 'px' : '0px'; |
| 550 |
$color = '' !== $color ? esc_attr( $color ) : 'rgba(0,0,0,0.5)'; |
| 551 |
|
| 552 |
$val = $h . ' ' . $v . ' ' . $blur . ' ' . $spread . ' ' . $color; |
| 553 |
if ( 'inset' === $pos ) { |
| 554 |
$val = 'inset ' . $val; |
| 555 |
} |
| 556 |
return $val; |
| 557 |
}; |
| 558 |
|
| 559 |
// Helper: render border CSS for a prefix (normal or hover) |
| 560 |
$render_border_css = function ( $attrs, $prefix_type, $prefix_width, $prefix_radius, $shadow_prefix, &$target_css, $devices ) use ( $build_shadow ) { |
| 561 |
$border_type = ! empty( $attrs[ $prefix_type ] ) ? $attrs[ $prefix_type ] : ''; |
| 562 |
|
| 563 |
// Border type + width + color |
| 564 |
if ( '' !== $border_type && 'none' !== $border_type ) { |
| 565 |
$target_css['desktop'][] = 'border-style:' . esc_attr( $border_type ) . ' !important'; |
| 566 |
|
| 567 |
// Border color |
| 568 |
$color_key = str_replace( 'Type', 'Color', $prefix_type ); |
| 569 |
if ( ! empty( $attrs[ $color_key ] ) ) { |
| 570 |
$target_css['desktop'][] = 'border-color:' . esc_attr( $attrs[ $color_key ] ) . ' !important'; |
| 571 |
} |
| 572 |
|
| 573 |
// Border width (responsive, per side) |
| 574 |
foreach ( $devices as $dev ) { |
| 575 |
$suffix = 'desktop' === $dev ? '' : ( 'tablet' === $dev ? 'Tablet' : 'Mobile' ); |
| 576 |
foreach ( array( 'Top', 'Right', 'Bottom', 'Left' ) as $side ) { |
| 577 |
$val = isset( $attrs[ $prefix_width . $side . $suffix ] ) ? $attrs[ $prefix_width . $side . $suffix ] : ''; |
| 578 |
$unit = isset( $attrs[ $prefix_width . $side . $suffix . 'Unit' ] ) ? $attrs[ $prefix_width . $side . $suffix . 'Unit' ] : 'px'; |
| 579 |
if ( '' !== $val && '' !== trim( (string) $val ) ) { |
| 580 |
$target_css[ $dev ][] = 'border-' . strtolower( $side ) . '-width:' . $val . $unit . ' !important'; |
| 581 |
} |
| 582 |
} |
| 583 |
} |
| 584 |
} elseif ( 'none' === $border_type ) { |
| 585 |
$target_css['desktop'][] = 'border:none !important'; |
| 586 |
} |
| 587 |
|
| 588 |
// Border radius (responsive, per corner) |
| 589 |
foreach ( $devices as $dev ) { |
| 590 |
$suffix = 'desktop' === $dev ? '' : ( 'tablet' === $dev ? 'Tablet' : 'Mobile' ); |
| 591 |
$radius_parts = array(); |
| 592 |
foreach ( array( 'Top', 'Right', 'Bottom', 'Left' ) as $side ) { |
| 593 |
$val = isset( $attrs[ $prefix_radius . $side . $suffix ] ) ? $attrs[ $prefix_radius . $side . $suffix ] : ''; |
| 594 |
$unit = isset( $attrs[ $prefix_radius . $side . $suffix . 'Unit' ] ) ? $attrs[ $prefix_radius . $side . $suffix . 'Unit' ] : 'px'; |
| 595 |
if ( '' !== $val && '' !== trim( (string) $val ) ) { |
| 596 |
$radius_parts[ $side ] = $val . $unit; |
| 597 |
} |
| 598 |
} |
| 599 |
if ( ! empty( $radius_parts ) ) { |
| 600 |
// Map Top/Right/Bottom/Left to border-radius corners: TL TR BR BL |
| 601 |
$tl = isset( $radius_parts['Top'] ) ? $radius_parts['Top'] : '0px'; |
| 602 |
$tr = isset( $radius_parts['Right'] ) ? $radius_parts['Right'] : '0px'; |
| 603 |
$br = isset( $radius_parts['Bottom'] ) ? $radius_parts['Bottom'] : '0px'; |
| 604 |
$bl = isset( $radius_parts['Left'] ) ? $radius_parts['Left'] : '0px'; |
| 605 |
$target_css[ $dev ][] = 'border-radius:' . $tl . ' ' . $tr . ' ' . $br . ' ' . $bl . ' !important'; |
| 606 |
} |
| 607 |
} |
| 608 |
|
| 609 |
// Box shadow |
| 610 |
$shadow_val = $build_shadow( $attrs, $shadow_prefix ); |
| 611 |
if ( '' !== $shadow_val ) { |
| 612 |
$target_css['desktop'][] = 'box-shadow:' . $shadow_val . ' !important'; |
| 613 |
} |
| 614 |
}; |
| 615 |
|
| 616 |
// Normal border |
| 617 |
$render_border_css( $attrs, 'bkbgBorderType', 'bkbgBorderWidth', 'bkbgBorderRadius', 'bkbgShadow', $css, $devices ); |
| 618 |
|
| 619 |
// Hover border |
| 620 |
$render_border_css( $attrs, 'bkbgBorderHoverType', 'bkbgBorderHoverWidth', 'bkbgBorderHoverRadius', 'bkbgShadowHover', $hover_css, $devices ); |
| 621 |
|
| 622 |
// ── Responsive visibility ── |
| 623 |
$hide_desktop = ! empty( $attrs['bkbgHideDesktop'] ); |
| 624 |
$hide_tablet = ! empty( $attrs['bkbgHideTablet'] ); |
| 625 |
$hide_mobile = ! empty( $attrs['bkbgHideMobile'] ); |
| 626 |
|
| 627 |
// Check if hover styles exist |
| 628 |
$has_hover = ! empty( $hover_css['desktop'] ) || ! empty( $hover_css['tablet'] ) || ! empty( $hover_css['mobile'] ); |
| 629 |
|
| 630 |
// Any styles to output? |
| 631 |
$has_responsive = $hide_desktop || $hide_tablet || $hide_mobile; |
| 632 |
$has_styles = ! empty( $css['desktop'] ) || ! empty( $css['tablet'] ) || ! empty( $css['mobile'] ) || $has_hover || $has_responsive; |
| 633 |
$has_id = ! empty( $attrs['bkbgCssId'] ); |
| 634 |
$has_cls = ! empty( $attrs['bkbgCssClasses'] ); |
| 635 |
|
| 636 |
if ( ! $has_styles && ! $has_id && ! $has_cls ) { |
| 637 |
return $block_content; |
| 638 |
} |
| 639 |
|
| 640 |
// Generate a unique class for targeting this specific block instance |
| 641 |
$unique = 'bkbg-adv-' . substr( md5( serialize( $attrs ) . wp_rand() ), 0, 8 ); |
| 642 |
|
| 643 |
// Inject unique class into the first HTML tag |
| 644 |
$block_content = preg_replace( |
| 645 |
'/(^\s*<[a-zA-Z][^>]*\bclass\s*=\s*")/', |
| 646 |
'$1' . esc_attr( $unique ) . ' ', |
| 647 |
$block_content, |
| 648 |
1, |
| 649 |
$count |
| 650 |
); |
| 651 |
if ( ! $count ) { |
| 652 |
// No class attribute found — add one |
| 653 |
$block_content = preg_replace( |
| 654 |
'/(^\s*<[a-zA-Z][^\s>]*)/', |
| 655 |
'$1 class="' . esc_attr( $unique ) . '"', |
| 656 |
$block_content, |
| 657 |
1 |
| 658 |
); |
| 659 |
} |
| 660 |
|
| 661 |
// Inject CSS ID |
| 662 |
if ( $has_id ) { |
| 663 |
$safe_id = esc_attr( $attrs['bkbgCssId'] ); |
| 664 |
$block_content = preg_replace( |
| 665 |
'/(^\s*<[a-zA-Z][^>]*)/', |
| 666 |
'$1 id="' . $safe_id . '"', |
| 667 |
$block_content, |
| 668 |
1 |
| 669 |
); |
| 670 |
} |
| 671 |
|
| 672 |
// Inject CSS Classes |
| 673 |
if ( $has_cls ) { |
| 674 |
$safe_cls = esc_attr( $attrs['bkbgCssClasses'] ); |
| 675 |
$block_content = preg_replace( |
| 676 |
'/(^\s*<[a-zA-Z][^>]*\bclass\s*=\s*")/', |
| 677 |
'$1' . $safe_cls . ' ', |
| 678 |
$block_content, |
| 679 |
1 |
| 680 |
); |
| 681 |
} |
| 682 |
|
| 683 |
// Build <style> tag |
| 684 |
if ( $has_styles ) { |
| 685 |
$sel = '.' . $unique; |
| 686 |
$style = ''; |
| 687 |
|
| 688 |
if ( ! empty( $css['desktop'] ) ) { |
| 689 |
$style .= $sel . '{' . implode( ';', $css['desktop'] ) . '}'; |
| 690 |
} |
| 691 |
if ( ! empty( $css['tablet'] ) ) { |
| 692 |
$style .= '@media(max-width:1024px){' . $sel . '{' . implode( ';', $css['tablet'] ) . '}}'; |
| 693 |
} |
| 694 |
if ( ! empty( $css['mobile'] ) ) { |
| 695 |
$style .= '@media(max-width:767px){' . $sel . '{' . implode( ';', $css['mobile'] ) . '}}'; |
| 696 |
} |
| 697 |
|
| 698 |
// Hover rules |
| 699 |
if ( ! empty( $hover_css['desktop'] ) ) { |
| 700 |
$style .= $sel . ':hover{' . implode( ';', $hover_css['desktop'] ) . '}'; |
| 701 |
} |
| 702 |
if ( ! empty( $hover_css['tablet'] ) ) { |
| 703 |
$style .= '@media(max-width:1024px){' . $sel . ':hover{' . implode( ';', $hover_css['tablet'] ) . '}}'; |
| 704 |
} |
| 705 |
if ( ! empty( $hover_css['mobile'] ) ) { |
| 706 |
$style .= '@media(max-width:767px){' . $sel . ':hover{' . implode( ';', $hover_css['mobile'] ) . '}}'; |
| 707 |
} |
| 708 |
|
| 709 |
// Responsive visibility: hide on specific devices |
| 710 |
if ( $hide_desktop ) { |
| 711 |
// Hide on desktop (>1024px) |
| 712 |
$style .= '@media(min-width:1025px){' . $sel . '{display:none !important}}'; |
| 713 |
} |
| 714 |
if ( $hide_tablet ) { |
| 715 |
// Hide on tablet (768–1024px) |
| 716 |
$style .= '@media(min-width:768px) and (max-width:1024px){' . $sel . '{display:none !important}}'; |
| 717 |
} |
| 718 |
if ( $hide_mobile ) { |
| 719 |
// Hide on mobile (≤767px) |
| 720 |
$style .= '@media(max-width:767px){' . $sel . '{display:none !important}}'; |
| 721 |
} |
| 722 |
|
| 723 |
$block_content .= '<style>' . $style . '</style>'; |
| 724 |
} |
| 725 |
|
| 726 |
return $block_content; |
| 727 |
}, 10, 2 ); |
| 728 |
|
| 729 |
/** |
| 730 |
* Register REST API endpoint for Post Grid block. |
| 731 |
* |
| 732 |
* Route: /wp-json/blockenberg/v1/post-grid |
| 733 |
* Method: GET |
| 734 |
* Params: |
| 735 |
* - type: string (posts|pages|any CPT) |
| 736 |
* - orderby: string (date|title|comment_count|...) |
| 737 |
* - order: string (asc|desc) |
| 738 |
* - per_page: int |
| 739 |
* - offset: int |
| 740 |
* - page: int (1-based) |
| 741 |
* - excerpt_len: int (optional) |
| 742 |
*/ |
| 743 |
add_action( 'rest_api_init', function () { |
| 744 |
register_rest_route( 'blockenberg/v1', '/post-grid', array( |
| 745 |
'methods' => 'GET', |
| 746 |
'permission_callback' => '__return_true', |
| 747 |
'args' => array( |
| 748 |
'type' => array( 'sanitize_callback' => 'sanitize_key' ), |
| 749 |
'orderby' => array( 'sanitize_callback' => 'sanitize_key' ), |
| 750 |
'order' => array( 'sanitize_callback' => 'sanitize_text_field' ), |
| 751 |
'per_page' => array( 'sanitize_callback' => 'absint' ), |
| 752 |
'offset' => array( 'sanitize_callback' => 'absint' ), |
| 753 |
'page' => array( 'sanitize_callback' => 'absint' ), |
| 754 |
'excerpt_len' => array( 'sanitize_callback' => 'absint' ), |
| 755 |
), |
| 756 |
'callback' => function ( WP_REST_Request $request ) { |
| 757 |
$requested_type = sanitize_key( $request->get_param( 'type' ) ?: 'post' ); |
| 758 |
if ( 'posts' === $requested_type ) { |
| 759 |
$requested_type = 'post'; |
| 760 |
} elseif ( 'pages' === $requested_type ) { |
| 761 |
$requested_type = 'page'; |
| 762 |
} |
| 763 |
|
| 764 |
$public_post_types = get_post_types( array( 'public' => true ), 'names' ); |
| 765 |
$post_type = in_array( $requested_type, $public_post_types, true ) ? $requested_type : 'post'; |
| 766 |
|
| 767 |
$requested_orderby = sanitize_key( $request->get_param( 'orderby' ) ?: 'date' ); |
| 768 |
$allowed_orderby = array( 'date', 'title', 'modified', 'comment_count', 'rand', 'menu_order' ); |
| 769 |
$orderby = in_array( $requested_orderby, $allowed_orderby, true ) ? $requested_orderby : 'date'; |
| 770 |
$order = strtolower( (string) $request->get_param( 'order' ) ) === 'asc' ? 'ASC' : 'DESC'; |
| 771 |
$per_page = max( 1, absint( $request->get_param( 'per_page' ) ?: 6 ) ); |
| 772 |
$per_page = min( 50, $per_page ); |
| 773 |
$offset = max( 0, absint( $request->get_param( 'offset' ) ?: 0 ) ); |
| 774 |
$offset = min( 5000, $offset ); |
| 775 |
$page = max( 1, absint( $request->get_param( 'page' ) ?: 1 ) ); |
| 776 |
$page = min( 200, $page ); |
| 777 |
$excerpt_len = max( 5, absint( $request->get_param( 'excerpt_len' ) ?: 18 ) ); |
| 778 |
$excerpt_len = min( 80, $excerpt_len ); |
| 779 |
|
| 780 |
// Short cache for public non-product queries. |
| 781 |
$is_product_query = ( 'product' === $post_type ); |
| 782 |
$cache_key = ''; |
| 783 |
if ( ! $is_product_query ) { |
| 784 |
$cache_key = 'bkbg_post_grid_' . md5( wp_json_encode( array( |
| 785 |
'type' => $post_type, |
| 786 |
'orderby' => $orderby, |
| 787 |
'order' => $order, |
| 788 |
'per_page' => $per_page, |
| 789 |
'offset' => $offset, |
| 790 |
'page' => $page, |
| 791 |
'excerpt_len' => $excerpt_len, |
| 792 |
) ) ); |
| 793 |
$cached = get_transient( $cache_key ); |
| 794 |
if ( is_array( $cached ) ) { |
| 795 |
return rest_ensure_response( $cached ); |
| 796 |
} |
| 797 |
} |
| 798 |
|
| 799 |
$q = new WP_Query( array( |
| 800 |
'post_type' => $post_type, |
| 801 |
'post_status' => 'publish', |
| 802 |
'orderby' => $orderby, |
| 803 |
'order' => $order, |
| 804 |
'posts_per_page' => $per_page, |
| 805 |
'offset' => $offset + ( ( $page - 1 ) * $per_page ), |
| 806 |
'ignore_sticky_posts' => true, |
| 807 |
'no_found_rows' => true, |
| 808 |
) ); |
| 809 |
|
| 810 |
$posts = array(); |
| 811 |
foreach ( $q->posts as $p ) { |
| 812 |
$post_id = $p->ID; |
| 813 |
$title = wp_strip_all_tags( get_the_title( $post_id ) ); |
| 814 |
$link = esc_url_raw( get_permalink( $post_id ) ); |
| 815 |
$image = esc_url_raw( (string) get_the_post_thumbnail_url( $post_id, 'medium_large' ) ); |
| 816 |
$date = wp_strip_all_tags( (string) get_the_date( '', $post_id ) ); |
| 817 |
$author = sanitize_text_field( (string) get_the_author_meta( 'display_name', $p->post_author ) ); |
| 818 |
$meta = trim( $date . ' · ' . $author ); |
| 819 |
$raw = get_post_field( 'post_excerpt', $post_id ); |
| 820 |
if ( '' === $raw ) { |
| 821 |
$raw = get_post_field( 'post_content', $post_id ); |
| 822 |
} |
| 823 |
$excerpt = wp_strip_all_tags( wp_trim_words( wp_strip_all_tags( $raw ), $excerpt_len, '…' ) ); |
| 824 |
|
| 825 |
$item = array( |
| 826 |
'id' => $post_id, |
| 827 |
'title' => $title, |
| 828 |
'link' => $link, |
| 829 |
'image' => $image, |
| 830 |
'meta' => $meta, |
| 831 |
'excerpt' => $excerpt, |
| 832 |
); |
| 833 |
|
| 834 |
if ( 'product' === $post_type && function_exists( 'wc_get_product' ) ) { |
| 835 |
$product = wc_get_product( $post_id ); |
| 836 |
if ( $product ) { |
| 837 |
$item['price_html'] = wp_kses_post( $product->get_price_html() ); |
| 838 |
$item['add_to_cart'] = esc_url_raw( $product->add_to_cart_url() ); |
| 839 |
} |
| 840 |
} |
| 841 |
|
| 842 |
$posts[] = $item; |
| 843 |
} |
| 844 |
|
| 845 |
$payload = array( 'posts' => $posts ); |
| 846 |
|
| 847 |
if ( ! $is_product_query && '' !== $cache_key ) { |
| 848 |
set_transient( $cache_key, $payload, 60 ); |
| 849 |
} |
| 850 |
|
| 851 |
return rest_ensure_response( $payload ); |
| 852 |
}, |
| 853 |
) ); |
| 854 |
} ); |
| 855 |
|
| 856 |
/** |
| 857 |
* Simple local newsletter subscribe endpoint. |
| 858 |
* Stores emails in an option array; extend as needed (e.g., to a custom table). |
| 859 |
*/ |
| 860 |
add_action( 'rest_api_init', function () { |
| 861 |
register_rest_route( 'blockenberg/v1', '/subscribe', array( |
| 862 |
'methods' => 'POST', |
| 863 |
'permission_callback' => '__return_true', |
| 864 |
'args' => array( |
| 865 |
'email' => array( 'sanitize_callback' => 'sanitize_email' ), |
| 866 |
'website' => array( 'sanitize_callback' => 'sanitize_text_field' ), |
| 867 |
), |
| 868 |
'callback' => function ( WP_REST_Request $request ) { |
| 869 |
$payload = $request->get_json_params(); |
| 870 |
if ( ! is_array( $payload ) ) { |
| 871 |
$payload = array(); |
| 872 |
} |
| 873 |
|
| 874 |
// Basic rate limiting by IP to reduce spam / abuse. |
| 875 |
$ip = ''; |
| 876 |
if ( isset( $_SERVER['REMOTE_ADDR'] ) ) { |
| 877 |
$ip = sanitize_text_field( wp_unslash( (string) $_SERVER['REMOTE_ADDR'] ) ); |
| 878 |
} |
| 879 |
if ( '' !== $ip ) { |
| 880 |
$key = 'bkbg_subscribe_' . md5( $ip ); |
| 881 |
$rl = get_transient( $key ); |
| 882 |
if ( is_array( $rl ) && isset( $rl['count'], $rl['start'] ) ) { |
| 883 |
if ( $rl['count'] >= 5 ) { |
| 884 |
return new WP_Error( 'rate_limited', __( 'Too many requests. Please try again later.', 'blockenberg' ), array( 'status' => 429 ) ); |
| 885 |
} |
| 886 |
$rl['count']++; |
| 887 |
set_transient( $key, $rl, 60 ); |
| 888 |
} else { |
| 889 |
set_transient( $key, array( 'count' => 1, 'start' => time() ), 60 ); |
| 890 |
} |
| 891 |
} |
| 892 |
|
| 893 |
// Optional honeypot field (bots tend to fill it). |
| 894 |
$honeypot = isset( $payload['website'] ) ? sanitize_text_field( (string) $payload['website'] ) : ''; |
| 895 |
if ( '' !== $honeypot ) { |
| 896 |
return rest_ensure_response( array( 'ok' => true ) ); |
| 897 |
} |
| 898 |
|
| 899 |
$email = isset( $payload['email'] ) ? sanitize_email( (string) $payload['email'] ) : ''; |
| 900 |
if ( empty( $email ) || ! is_email( $email ) ) { |
| 901 |
return new WP_Error( 'invalid_email', __( 'Invalid email address', 'blockenberg' ), array( 'status' => 400 ) ); |
| 902 |
} |
| 903 |
|
| 904 |
$list = get_option( 'blockenberg_newsletter_subscribers', array() ); |
| 905 |
if ( ! is_array( $list ) ) { |
| 906 |
$list = array(); |
| 907 |
} |
| 908 |
|
| 909 |
// Prevent unbounded option growth. |
| 910 |
if ( count( $list ) >= 5000 ) { |
| 911 |
return new WP_Error( 'storage_full', __( 'Subscriber list is full.', 'blockenberg' ), array( 'status' => 503 ) ); |
| 912 |
} |
| 913 |
|
| 914 |
if ( ! in_array( $email, $list, true ) ) { |
| 915 |
$list[] = $email; |
| 916 |
update_option( 'blockenberg_newsletter_subscribers', $list, false ); |
| 917 |
} |
| 918 |
return rest_ensure_response( array( 'ok' => true ) ); |
| 919 |
}, |
| 920 |
) ); |
| 921 |
} ); |