PluginProbe
Kit (formerly ConvertKit) – Email Newsletter, Email Marketing, Membership, Subscribers and Landing Pages / trunk
Kit (formerly ConvertKit) – Email Newsletter, Email Marketing, Membership, Subscribers and Landing Pages vtrunk
3.4.1 3.4.0 3.3.9 3.3.8 3.3.7 3.3.6 3.3.5 3.3.4 3.3.3 3.3.2 3.3.1 2.2.0 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.2.6 2.2.7 2.2.8 2.2.9 2.3.0 2.3.1 2.3.2 2.3.3 All 194 releases
convertkit / includes / blocks / class-convertkit-block.php

class-convertkit-block.php in Kit (formerly ConvertKit) – Email Newsletter, Email Marketing, Membership, Subscribers and Landing Pages trunk, at includes/blocks/class-convertkit-block.php

570 lines 14.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * ConvertKit Block class.
4 *
5 * @package ConvertKit
6 * @author ConvertKit
7 */
8
9 /**
10 * ConvertKit Block definition for Gutenberg and Shortcode.
11 *
12 * @package ConvertKit
13 * @author ConvertKit
14 */
15 class ConvertKit_Block {
16
17 /**
18 * Registers this block with the ConvertKit Plugin.
19 *
20 * @since 1.9.6
21 *
22 * @param array $blocks Blocks to Register.
23 * @return array Blocks to Register
24 */
25 public function register( $blocks ) {
26
27 // If the request is for the frontend, return the minimum block definition required
28 // to register and render the block on the frontend site using register_block_type().
29 if ( ! $this->is_admin_frontend_editor_or_admin_rest_request() ) {
30 $blocks[ $this->get_name() ] = array(
31 'title' => $this->get_title(),
32 'icon' => $this->get_icon(),
33 'attributes' => $this->get_attributes(),
34 'render_callback' => array( $this, 'render' ),
35 );
36
37 return $blocks;
38 }
39
40 // Request is for the WordPress Administration, frontend editor or REST API request.
41 // Register the full block definition, including fields, panels, default values and supports.
42 $blocks[ $this->get_name() ] = array_merge(
43 $this->get_overview(),
44 array(
45 'name' => $this->get_name(),
46 'fields' => $this->get_fields(),
47 'attributes' => $this->get_attributes(),
48 'supports' => $this->get_supports(),
49 'panels' => $this->get_panels(),
50 'default_values' => $this->get_default_values(),
51 )
52 );
53
54 return $blocks;
55
56 }
57
58 /**
59 * Registers this block's MCP abilities.
60 *
61 * @since 3.4.0
62 *
63 * @param array $abilities Abilities to Register.
64 * @return array
65 */
66 public function register_abilities( $abilities ) {
67
68 return array_merge(
69 $abilities,
70 array(
71 'kit/' . $this->get_name() . '-list' => new ConvertKit_MCP_Ability_Content_List( $this ),
72 'kit/' . $this->get_name() . '-insert' => new ConvertKit_MCP_Ability_Content_Insert( $this ),
73 'kit/' . $this->get_name() . '-update' => new ConvertKit_MCP_Ability_Content_Update( $this ),
74 'kit/' . $this->get_name() . '-delete' => new ConvertKit_MCP_Ability_Content_Delete( $this ),
75 )
76 );
77
78 }
79
80 /**
81 * Returns this block's programmatic name, excluding the convertkit- prefix.
82 *
83 * @since 1.9.6
84 */
85 public function get_name() {
86
87 /**
88 * This will register as:
89 * - a shortcode, with the name [convertkit_form].
90 * - a shortcode, with the name [convertkit], for backward compat.
91 * - a Gutenberg block, with the name convertkit/form.
92 */
93 return '';
94
95 }
96
97 /**
98 * Returns this block's title.
99 *
100 * @since 3.1.1
101 */
102 public function get_title() {
103
104 return '';
105
106 }
107
108 /**
109 * Returns this block's plural title.
110 *
111 * @since 3.4.0
112 *
113 * @return string
114 */
115 public function get_title_plural() {
116
117 return '';
118
119 }
120
121 /**
122 * Returns this block's icon.
123 *
124 * @since 3.1.1
125 */
126 public function get_icon() {
127
128 return '';
129
130 }
131
132 /**
133 * Returns this block's Title, Icon, Categories, Keywords and properties.
134 *
135 * @since 1.9.6
136 *
137 * @return array
138 */
139 public function get_overview() {
140
141 return array();
142
143 }
144
145 /**
146 * Returns this block's Attributes
147 *
148 * @since 1.9.6.5
149 *
150 * @return array
151 */
152 public function get_attributes() {
153
154 return array();
155
156 }
157
158 /**
159 * Gutenberg: Returns supported built in attributes, such as
160 * className, color etc.
161 *
162 * @since 1.9.7.4
163 *
164 * @return array Supports
165 */
166 public function get_supports() {
167
168 return array(
169 'className' => true,
170 );
171
172 }
173
174 /**
175 * Returns this block's Fields
176 *
177 * @since 1.9.6
178 *
179 * @return array
180 */
181 public function get_fields() {
182
183 return array();
184
185 }
186
187 /**
188 * Returns this block's UI panels / sections.
189 *
190 * @since 1.9.6
191 *
192 * @return array
193 */
194 public function get_panels() {
195
196 return array();
197
198 }
199
200 /**
201 * Returns this block's Default Values
202 *
203 * @since 1.9.6
204 *
205 * @return array
206 */
207 public function get_default_values() {
208
209 return array();
210
211 }
212
213 /**
214 * Returns the given block's field's Default Value
215 *
216 * @since 1.9.6
217 *
218 * @param string $field Field Name.
219 * @return string
220 */
221 public function get_default_value( $field ) {
222
223 $defaults = $this->get_default_values();
224 if ( isset( $defaults[ $field ] ) ) {
225 return $defaults[ $field ];
226 }
227
228 return '';
229
230 }
231
232 /**
233 * Performs several transformation on a block's attributes, including:
234 * - sanitization
235 * - adding attributes with default values are missing but registered by the block
236 * - cast attribute values based on their defined type
237 *
238 * These steps are performed because the attributes may be defined by a shortcode,
239 * block or third party widget/page builder's block, each of which handle attributes
240 * slightly differently.
241 *
242 * Returns a standardised attributes array.
243 *
244 * @since 1.9.7.4
245 *
246 * @param array $atts Declared attributes.
247 * @return array All attributes, standardised.
248 */
249 public function sanitize_and_declare_atts( $atts ) {
250
251 // Sanitize attributes, merging with default values so that the array
252 // of attributes contains all expected keys for this block.
253 $atts = shortcode_atts(
254 $this->get_default_values(),
255 $this->sanitize_atts( $atts ),
256 $this->get_name()
257 );
258
259 // Fetch attribute definitions.
260 $atts_definitions = $this->get_attributes();
261
262 // Iterate through attributes, casting them based on their attribute definition.
263 foreach ( $atts as $att => $value ) {
264 // Skip if no definition exists for this attribute.
265 if ( ! array_key_exists( $att, $atts_definitions ) ) {
266 continue;
267 }
268
269 // Skip if no type exists for this attribute.
270 if ( ! array_key_exists( 'type', $atts_definitions[ $att ] ) ) {
271 continue;
272 }
273
274 // Cast, depending on the attribute type.
275 switch ( $atts_definitions[ $att ]['type'] ) {
276 case 'number':
277 $atts[ $att ] = (int) $value;
278 break;
279
280 case 'boolean':
281 $atts[ $att ] = (bool) $value;
282 break;
283
284 case 'string':
285 // If the attribute's value is empty, check if the default attribute has a value.
286 // If so, apply it now.
287 // shortcode_atts() will only do this if the attribute key isn't specified.
288 if ( empty( $value ) && ! empty( $this->get_default_value( $att ) ) ) {
289 $atts[ $att ] = $this->get_default_value( $att );
290 }
291 break;
292 }
293 }
294
295 // Remove some unused attributes, now they're declared above.
296 unset( $atts['style'], $atts['backgroundColor'], $atts['textColor'], $atts['className'] );
297
298 return $atts;
299
300 }
301
302 /**
303 * Removes any HTML that might be wrongly included in the shorcode attribute's values
304 * due to e.g. copy and pasting from Documentation or other examples.
305 *
306 * @since 1.9.6
307 *
308 * @param array $atts Block or shortcode attributes.
309 * @return array
310 */
311 public function sanitize_atts( $atts ) {
312
313 foreach ( $atts as $key => $value ) {
314 if ( is_array( $value ) ) {
315 continue;
316 }
317
318 $atts[ $key ] = wp_strip_all_tags( $value );
319 }
320
321 return $atts;
322
323 }
324
325 /**
326 * Builds CSS class(es) that might need to be added to the top level element's `class` attribute
327 * when using Gutenberg, to honor the block's styles and layout settings.
328 *
329 * @since 2.8.3
330 *
331 * @param array $additional_classes Additional classes to add to the block.
332 * @return array
333 */
334 public function get_css_classes( $additional_classes = array() ) {
335
336 // To avoid errors in get_block_wrapper_attributes() in non-block themes using the shortcode,
337 // tell WordPress that a block is being rendered.
338 // The attributes don't matter, as we send them to the render() function.
339 if ( class_exists( 'WP_Block_Supports' ) && is_null( WP_Block_Supports::$block_to_render ) ) { // @phpstan-ignore-line
340 WP_Block_Supports::$block_to_render = array(
341 'blockName' => 'convertkit/' . $this->get_name(),
342 'attrs' => array(),
343 'innerBlocks' => array(),
344 'innerHTML' => '',
345 'innerContent' => array(),
346 );
347 }
348
349 // Get the block wrapper attributes string.
350 $wrapper_attributes = get_block_wrapper_attributes(
351 array(
352 'class' => implode(
353 ' ',
354 array_merge(
355 array(
356 'convertkit-' . $this->get_name(),
357 ),
358 $additional_classes
359 )
360 ),
361 )
362 );
363
364 // Extract the class attribute from the wrapper attributes string, returning as an array.
365 // Extract just the class attribute value from the wrapper attributes string.
366 $classes = array();
367 if ( preg_match( '/class="([^"]*)"/', $wrapper_attributes, $matches ) ) {
368 $classes = explode( ' ', $matches[1] );
369 } else {
370 $classes = array(
371 'convertkit-' . $this->get_name(),
372 );
373 }
374
375 // Remove some classes WordPress adds that we don't want, as they break the layout.
376 $classes = array_diff( $classes, array( 'alignfull', 'wp-block-post-content' ) );
377
378 return $classes;
379
380 }
381
382 /**
383 * Builds inline CSS style(s) that might need to be added to the top level element's `style` attribute
384 * when using Gutenberg, a shortcode or third party page builder module / widget.
385 *
386 * @since 2.8.3
387 *
388 * @param array $atts Block or shortcode attributes.
389 * @return array
390 */
391 public function get_css_styles( $atts ) {
392
393 // To avoid errors in get_block_wrapper_attributes() in non-block themes using the shortcode,
394 // tell WordPress that a block is being rendered.
395 // The attributes don't matter, as we send them to the render() function.
396 if ( class_exists( 'WP_Block_Supports' ) && is_null( WP_Block_Supports::$block_to_render ) ) { // @phpstan-ignore-line
397 WP_Block_Supports::$block_to_render = array(
398 'blockName' => 'convertkit/' . $this->get_name(),
399 'attrs' => array(),
400 'innerBlocks' => array(),
401 'innerHTML' => '',
402 'innerContent' => array(),
403 );
404 }
405
406 $styles = array();
407
408 // Get the block wrapper attributes string, extracting any styles that the block has set,
409 // such as margin, padding or block spacing.
410 $wrapper_attributes = get_block_wrapper_attributes();
411 if ( preg_match( '/style="([^"]*)"/', $wrapper_attributes, $matches ) ) {
412 return array_filter( explode( ';', $matches[1] ) );
413 }
414
415 // If here, no block styles were found.
416 // This might be a shortcode or third party page builder module / widget that has
417 // specific attributes set.
418 if ( isset( $atts['text_color'] ) && ! empty( $atts['text_color'] ) ) {
419 $styles[] = 'color:' . $atts['text_color'];
420 }
421 if ( isset( $atts['background_color'] ) && ! empty( $atts['background_color'] ) ) {
422 $styles[] = 'background-color:' . $atts['background_color'];
423 }
424
425 return $styles;
426
427 }
428
429 /**
430 * Returns the given block / shortcode attributes array as HTML data-* attributes, which can be output
431 * in a block's container.
432 *
433 * @since 1.9.7.6
434 *
435 * @param array $atts Block or shortcode attributes.
436 * @return string Block or shortcode attributes
437 */
438 public function get_atts_as_html_data_attributes( $atts ) {
439
440 // Define attributes provided by Gutenberg, which will be skipped, such as
441 // styling.
442 $skip_keys = array(
443 'backgroundColor',
444 'textColor',
445 '_css_styles',
446 );
447
448 // Define a blank string to build the data-* attributes in.
449 $data = '';
450
451 foreach ( $atts as $key => $value ) {
452 // Skip built in attributes provided by Gutenberg.
453 if ( in_array( $key, $skip_keys, true ) ) {
454 continue;
455 }
456
457 // Skip empty values.
458 if ( empty( $value ) ) {
459 continue;
460 }
461
462 // Append to data string, replacing underscores with hyphens in the key name.
463 $data .= ' data-' . strtolower( str_replace( '_', '-', $key ) ) . '="' . esc_attr( $value ) . '"';
464 }
465
466 return trim( $data );
467
468 }
469
470 /**
471 * Determines if the request is a WordPress REST API request
472 * made by a logged in WordPress user who has the capability to edit posts.
473 *
474 * @since 3.1.0
475 *
476 * @return bool
477 */
478 public function is_admin_rest_request() {
479
480 return defined( 'REST_REQUEST' ) && REST_REQUEST && current_user_can( 'edit_posts' );
481
482 }
483
484 /**
485 * Determines if the request is for the WordPress Administration, frontend editor or REST API request.
486 *
487 * @since 3.1.0
488 *
489 * @return bool
490 */
491 public function is_admin_frontend_editor_or_admin_rest_request() {
492
493 return WP_ConvertKit()->is_admin_or_frontend_editor() || $this->is_admin_rest_request();
494
495 }
496
497 /**
498 * Determines if the request for the block is from the block editor or the frontend site.
499 *
500 * @since 1.9.8.5
501 *
502 * @return bool
503 */
504 public function is_block_editor_request() {
505
506 // Return false if not a WordPress REST API request, which Gutenberg uses.
507 if ( ! $this->is_admin_rest_request() ) {
508 return false;
509 }
510
511 // Return false if the context parameter isn't edit.
512 if ( ! filter_has_var( INPUT_GET, 'context' ) ) {
513 return false;
514 }
515 if ( filter_input( INPUT_GET, 'context', FILTER_SANITIZE_FULL_SPECIAL_CHARS ) !== 'edit' ) {
516 return false;
517 }
518
519 // Request is for the block editor.
520 return true;
521
522 }
523
524 /**
525 * If the Block Visiblity Plugin is active, run the block through its conditions now.
526 * We don't wait for Block Visibility to do this, as it performs this on the
527 * `render_block` filter, by which time the code in this method has fully executed,
528 * meaning any non-inline Forms will have had their scripts added to the
529 * `convertkit_output_scripts_footer` hook.
530 * As a result, the non-inline Form will always display, regardless of whether
531 * Block Visibility's conditions are met.
532 * We deliberately don't output non-inline Forms in their block, instead deferring
533 * to the `convertkit_output_scripts_footer` hook, to ensure the non-inline Forms
534 * styling are not constrained by the Theme's width, layout or other properties.
535 *
536 * @since 2.6.6
537 *
538 * @param array $atts Block Attributes.
539 * @return bool Display Block
540 */
541 public function is_block_visible( $atts ) {
542
543 // Display the block if the Block Visibility Plugin isn't active.
544 if ( ! function_exists( '\BlockVisibility\Frontend\render_with_visibility' ) ) {
545 return true;
546 }
547
548 // Determine whether the block should display.
549 $display_block = \BlockVisibility\Frontend\render_with_visibility(
550 'block',
551 array(
552 'blockName' => 'convertkit-' . $this->get_name(),
553 'attrs' => $atts,
554 )
555 );
556
557 // If the content returned is a blank string, conditions on this block set
558 // by the user in the Block Visibility Plugin resulted in the block not displaying.
559 // Don't display it.
560 if ( empty( $display_block ) ) {
561 return false;
562 }
563
564 // If here, the block can be displayed.
565 return true;
566
567 }
568
569 }
570