PluginProbe
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor / 2.0.14
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor v2.0.14
2.0.14 2.0.13 2.0.12 2.0.11 2.0.10 2.0.9 trunk 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.0.8
← All changes | blockenberg.php +134 -10 2.0.7 → 2.0.14 View file →
@@ -1,9 +1,9 @@
1 1 <?php
2 2 /**
3 - * Plugin Name: Blockenberg
4 - * Description: Advanced Gutenberg Blocks for WordPress Block Editor
5 - * Version: 2.0.7
3 + * Plugin Name: Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor
4 + * Description: Advanced Gutenberg Blocks and an AI Agent for the WordPress Block Editor
5 + * Version: 2.0.14
6 6 * Author: Blockenberg
7 7 * Text Domain: blockenberg
8 8 * Domain Path: /languages
9 9 * License: GPLv2 or later
@@ -23,13 +23,32 @@
23 23 * Google Fonts list for the Typography Control.
24 24 */
25 25 require_once __DIR__ . '/assets/php/google-fonts.php';
26 26
27 +// Editable Gutenberg template pilot and shared opt-in motion settings.
28 +require_once __DIR__ . '/assets/php/motion.php';
29 +require_once __DIR__ . '/assets/php/template-catalog.php';
30 +require_once __DIR__ . '/assets/php/template-components.php';
31 +require_once __DIR__ . '/assets/php/template-dynamics.php';
32 +require_once __DIR__ . '/assets/php/template-preview.php';
33 +
27 34 /**
35 + * User Field block — dynamic PHP render for logged-in profile values.
36 + */
37 +require_once __DIR__ . '/blocks/user-field/render.php';
38 +
39 +/**
40 + * AI Agent — OpenRouter-powered chat panel inside the block editor.
41 + */
42 +require_once __DIR__ . '/assets/php/ai-assistant.php';
43 +require_once __DIR__ . '/assets/php/ai-external.php';
44 +
45 +/**
28 46 * Admin dashboard — Block Manager (enable / disable blocks).
29 47 */
30 48 if ( is_admin() ) {
31 49 require_once __DIR__ . '/assets/php/admin-dashboard.php';
50 + require_once __DIR__ . '/assets/php/admin-notices.php';
32 51 }
33 52
34 53 /**
35 54 * Enqueue common editor styles and scripts for all Blockenberg blocks
@@ -1094,10 +1113,98 @@
1094 1113 ) );
1095 1114 } );
1096 1115
1097 1116 /**
1117 + * Strip CR/LF and related sequences so values cannot inject mail headers.
1118 + *
1119 + * @param string $value Raw header fragment.
1120 + * @return string
1121 + */
1122 +function bkbg_sanitize_mail_header( $value ) {
1123 + $value = (string) $value;
1124 + $value = str_replace( array( "\r", "\n", '%0a', '%0d', '%0A', '%0D' ), '', $value );
1125 + return trim( sanitize_text_field( $value ) );
1126 +}
1127 +
1128 +/**
1129 + * HMAC signature for a contact-form recipient email.
1130 + * Prevents open-relay abuse: the client may only use a recipient that was
1131 + * signed server-side when the block was rendered.
1132 + *
1133 + * @param string $email Recipient email.
1134 + * @return string Hex HMAC or empty string if invalid.
1135 + */
1136 +function bkbg_contact_recipient_sig( $email ) {
1137 + $email = strtolower( sanitize_email( (string) $email ) );
1138 + if ( ! is_email( $email ) ) {
1139 + return '';
1140 + }
1141 + return hash_hmac( 'sha256', $email, wp_salt( 'auth' ) );
1142 +}
1143 +
1144 +/**
1145 + * Verify a contact-form recipient signature.
1146 + *
1147 + * @param string $email Recipient email from the client.
1148 + * @param string $sig HMAC from data-recipient-sig.
1149 + * @return bool
1150 + */
1151 +function bkbg_verify_contact_recipient( $email, $sig ) {
1152 + $email = strtolower( sanitize_email( (string) $email ) );
1153 + if ( ! is_email( $email ) || ! is_string( $sig ) || '' === $sig ) {
1154 + return false;
1155 + }
1156 + $expected = bkbg_contact_recipient_sig( $email );
1157 + return ( '' !== $expected && hash_equals( $expected, $sig ) );
1158 +}
1159 +
1160 +/**
1161 + * Inject a server-signed recipient HMAC into Contact Form markup on render.
1162 + * Existing posts do not need to be re-saved — the signature is added at runtime.
1163 + */
1164 +add_filter( 'render_block', function ( $block_content, $block ) {
1165 + if ( empty( $block['blockName'] ) || 'blockenberg/contact-form' !== $block['blockName'] ) {
1166 + return $block_content;
1167 + }
1168 + if ( ! is_string( $block_content ) || '' === $block_content ) {
1169 + return $block_content;
1170 + }
1171 +
1172 + $attrs = isset( $block['attrs'] ) && is_array( $block['attrs'] ) ? $block['attrs'] : array();
1173 + $recipient = isset( $attrs['recipientEmail'] ) ? sanitize_email( (string) $attrs['recipientEmail'] ) : '';
1174 + if ( ! is_email( $recipient ) ) {
1175 + return $block_content;
1176 + }
1177 +
1178 + $sig = bkbg_contact_recipient_sig( $recipient );
1179 + if ( '' === $sig ) {
1180 + return $block_content;
1181 + }
1182 +
1183 + // Replace existing sig if present, otherwise inject onto the first opening tag.
1184 + if ( false !== strpos( $block_content, 'data-recipient-sig=' ) ) {
1185 + $block_content = preg_replace(
1186 + '/\sdata-recipient-sig=(["\'])[^"\']*\1/',
1187 + ' data-recipient-sig="' . esc_attr( $sig ) . '"',
1188 + $block_content,
1189 + 1
1190 + );
1191 + } else {
1192 + $block_content = preg_replace(
1193 + '/^\s*(<[a-zA-Z][^>]*)/',
1194 + '$1 data-recipient-sig="' . esc_attr( $sig ) . '"',
1195 + $block_content,
1196 + 1
1197 + );
1198 + }
1199 +
1200 + return $block_content;
1201 +}, 10, 2 );
1202 +
1203 +/**
1098 1204 * Contact Form endpoint — POST /wp-json/blockenberg/v1/contact
1099 1205 * Sends an email via wp_mail() to the admin or a custom recipient stored in the block.
1206 + * Custom recipients require a valid server-issued HMAC (data-recipient-sig).
1100 1207 */
1101 1208 add_action( 'rest_api_init', function () {
1102 1209 register_rest_route( 'blockenberg/v1', '/contact', array(
1103 1210 'methods' => 'POST',
@@ -1145,15 +1252,23 @@
1145 1252 if ( empty( $message ) ) {
1146 1253 return new WP_Error( 'empty_message', __( 'Message is required.', 'blockenberg' ), array( 'status' => 400 ) );
1147 1254 }
1148 1255
1149 - // Determine recipient and subject.
1150 - $recipient = isset( $payload['recipient'] ) ? sanitize_email( (string) $payload['recipient'] ) : '';
1151 - if ( ! is_email( $recipient ) ) {
1152 - $recipient = get_option( 'admin_email' );
1256 + // Recipient: only accept a client-supplied address when it carries a
1257 + // valid server HMAC. Otherwise always fall back to admin_email (no open relay).
1258 + $admin_email = sanitize_email( (string) get_option( 'admin_email' ) );
1259 + $recipient = $admin_email;
1260 + $requested = isset( $payload['recipient'] ) ? sanitize_email( (string) $payload['recipient'] ) : '';
1261 + $sig = isset( $payload['recipientSig'] ) ? (string) $payload['recipientSig'] : '';
1262 + if ( is_email( $requested ) && bkbg_verify_contact_recipient( $requested, $sig ) ) {
1263 + $recipient = strtolower( $requested );
1153 1264 }
1154 - $subject = isset( $payload['subject'] ) ? sanitize_text_field( (string) $payload['subject'] ) : __( 'New Contact Form Submission', 'blockenberg' );
1155 1265
1266 + $subject = isset( $payload['subject'] ) ? bkbg_sanitize_mail_header( (string) $payload['subject'] ) : '';
1267 + if ( '' === $subject ) {
1268 + $subject = __( 'New Contact Form Submission', 'blockenberg' );
1269 + }
1270 +
1156 1271 // Build email body.
1157 1272 $body = "Name: {$name}\n";
1158 1273 $body .= "Email: {$email}\n";
1159 1274 if ( ! empty( $phone ) ) {
@@ -1160,11 +1275,20 @@
1160 1275 $body .= "Phone: {$phone}\n";
1161 1276 }
1162 1277 $body .= "\nMessage:\n{$message}\n";
1163 1278
1279 + // Reply-To: strip CR/LF from name; never allow header injection.
1280 + $safe_name = bkbg_sanitize_mail_header( $name );
1281 + $safe_name = str_replace( array( '"', '<', '>' ), '', $safe_name );
1282 + if ( '' !== $safe_name ) {
1283 + $reply_to = sprintf( 'Reply-To: %s <%s>', $safe_name, $email );
1284 + } else {
1285 + $reply_to = 'Reply-To: ' . $email;
1286 + }
1287 +
1164 1288 $headers = array(
1165 1289 'Content-Type: text/plain; charset=UTF-8',
1166 - "Reply-To: {$name} <{$email}>",
1290 + $reply_to,
1167 1291 );
1168 1292
1169 1293 $sent = wp_mail( $recipient, $subject, $body, $headers );
1170 1294
@@ -1174,5 +1298,5 @@
1174 1298
1175 1299 return rest_ensure_response( array( 'ok' => true ) );
1176 1300 },
1177 1301 ) );
1178 -} );
1302 +} );