PluginProbe
Kit (formerly ConvertKit) – Email Newsletter, Email Marketing, Membership, Subscribers and Landing Pages / 2.7.4
Kit (formerly ConvertKit) – Email Newsletter, Email Marketing, Membership, Subscribers and Landing Pages v2.7.4
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 / class-convertkit-resource-forms.php

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

526 lines 15.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * ConvertKit Forms Resource class.
4 *
5 * @package ConvertKit
6 * @author ConvertKit
7 */
8
9 /**
10 * Reads ConvertKit Forms from the options table, and refreshes
11 * ConvertKit Forms data stored locally from the API.
12 *
13 * @since 1.9.6
14 */
15 class ConvertKit_Resource_Forms extends ConvertKit_Resource_V4 {
16
17 /**
18 * Holds the Settings Key that stores site wide ConvertKit settings
19 *
20 * @var string
21 */
22 public $settings_name = 'convertkit_forms';
23
24 /**
25 * The type of resource
26 *
27 * @var string
28 */
29 public $type = 'forms';
30
31 /**
32 * Constructor.
33 *
34 * @since 1.9.8.4
35 *
36 * @param bool|string $context Context.
37 */
38 public function __construct( $context = false ) {
39
40 // Initialize the API if the Access Token has been defined in the Plugin Settings.
41 $settings = new ConvertKit_Settings();
42 if ( $settings->has_access_and_refresh_token() ) {
43 $this->api = new ConvertKit_API_V4(
44 CONVERTKIT_OAUTH_CLIENT_ID,
45 CONVERTKIT_OAUTH_CLIENT_REDIRECT_URI,
46 $settings->get_access_token(),
47 $settings->get_refresh_token(),
48 $settings->debug_enabled(),
49 $context
50 );
51 }
52
53 // Call parent initialization function.
54 parent::init();
55
56 }
57
58 /**
59 * Returns all inline forms based on the sort order.
60 *
61 * @since 2.7.3
62 *
63 * @return bool|array
64 */
65 public function get_inline() {
66
67 // If the ConvertKit WordPress Libraries are < 1.3.6 (e.g. loaded by an outdated
68 // addon), or a WordPress site updates this Plugin before other ConvertKit Plugins,
69 // get_by() won't be available and will cause an E_ERROR, crashing the site.
70 // @see https://wordpress.org/support/topic/error-1795/.
71 if ( ! method_exists( $this, 'get_by' ) ) { // @phpstan-ignore-line Older WordPress Libraries won't have this function.
72 return false;
73 }
74
75 return $this->get_by( 'format', array( 'inline' ) );
76
77 }
78
79 /**
80 * Returns whether any inline forms exist in the options table.
81 *
82 * @since 2.7.3
83 *
84 * @return bool
85 */
86 public function inline_exist() {
87
88 return (bool) $this->get_inline();
89
90 }
91
92 /**
93 * Returns all non-inline forms based on the sort order.
94 *
95 * @since 2.2.4
96 *
97 * @return bool|array
98 */
99 public function get_non_inline() {
100
101 // If the ConvertKit WordPress Libraries are < 1.3.6 (e.g. loaded by an outdated
102 // addon), or a WordPress site updates this Plugin before other ConvertKit Plugins,
103 // get_by() won't be available and will cause an E_ERROR, crashing the site.
104 // @see https://wordpress.org/support/topic/error-1795/.
105 if ( ! method_exists( $this, 'get_by' ) ) { // @phpstan-ignore-line Older WordPress Libraries won't have this function.
106 return false;
107 }
108
109 return $this->get_by( 'format', array( 'modal', 'slide in', 'sticky bar' ) );
110
111 }
112
113 /**
114 * Returns whether any non-inline forms exist in the options table.
115 *
116 * @since 2.2.4
117 *
118 * @return bool
119 */
120 public function non_inline_exist() {
121
122 if ( ! $this->get_non_inline() ) {
123 return false;
124 }
125
126 return true;
127
128 }
129
130 /**
131 * Determines if the given Form ID is a legacy Form or Landing Page.
132 *
133 * @since 2.5.0
134 *
135 * @param int $id Form or Landing Page ID.
136 */
137 public function is_legacy( $id ) {
138
139 // Get Form.
140 $form = $this->get_by_id( (int) $id );
141
142 // Return false if no Form exists.
143 if ( ! $form ) {
144 return false;
145 }
146
147 // If the `format` key exists, this is not a legacy Form.
148 if ( array_key_exists( 'format', $form ) ) {
149 return false;
150 }
151
152 return true;
153
154 }
155
156 /**
157 * Returns a <select> field populated with all forms, based on the given parameters.
158 *
159 * @since 2.3.9
160 *
161 * @param string $name Name.
162 * @param string $id ID.
163 * @param bool|array $css_classes <select> CSS class(es).
164 * @param string $selected_option <option> value to mark as selected.
165 * @param bool|array $prepend_options <option> elements to prepend before resources.
166 * @param bool|array $attributes <select> attributes.
167 * @param bool|string|array $description Description.
168 * @return string HTML Select Field
169 */
170 public function get_select_field_all( $name, $id, $css_classes, $selected_option, $prepend_options = false, $attributes = false, $description = false ) {
171
172 return $this->get_select_field(
173 $this->get(),
174 $name,
175 $id,
176 $css_classes,
177 $selected_option,
178 $prepend_options,
179 $attributes,
180 $description
181 );
182
183 }
184
185 /**
186 * Returns a <select> field populated with all non-inline forms, based on the given parameters.
187 *
188 * @since 2.3.9
189 *
190 * @param string $name Name.
191 * @param string $id ID.
192 * @param bool|array $css_classes <select> CSS class(es).
193 * @param array $selected_options <option> values to mark as selected.
194 * @param bool|array $prepend_options <option> elements to prepend before resources.
195 * @param bool|array $attributes <select> attributes.
196 * @param bool|string|array $description Description.
197 * @return string HTML Select Field
198 */
199 public function get_select_field_non_inline( $name, $id, $css_classes, $selected_options, $prepend_options = false, $attributes = false, $description = false ) {
200
201 return $this->get_multi_select_field(
202 $this->get_non_inline(),
203 $name,
204 $id,
205 $css_classes,
206 $selected_options,
207 $prepend_options,
208 $attributes,
209 $description
210 );
211
212 }
213
214 /**
215 * Returns a <select> field populated with the resources, based on the given parameters.
216 *
217 * @since 2.3.9
218 *
219 * @param array $forms Forms.
220 * @param string $name Name.
221 * @param string $id ID.
222 * @param bool|array $css_classes <select> CSS class(es).
223 * @param string $selected_option <option> value to mark as selected.
224 * @param bool|array $prepend_options <option> elements to prepend before resources.
225 * @param bool|array $attributes <select> attributes.
226 * @param bool|string|array $description Description.
227 * @return string HTML Select Field
228 */
229 private function get_select_field( $forms, $name, $id, $css_classes, $selected_option, $prepend_options = false, $attributes = false, $description = false ) {
230
231 $html = sprintf(
232 '<select name="%s" id="%s" class="%s"',
233 esc_attr( $name ),
234 esc_attr( $id ),
235 esc_attr( ( is_array( $css_classes ) ? implode( ' ', $css_classes ) : '' ) )
236 );
237
238 // Append any attributes.
239 if ( $attributes ) {
240 foreach ( $attributes as $key => $value ) {
241 $html .= sprintf(
242 ' %s="%s"',
243 esc_attr( $key ),
244 esc_attr( $value )
245 );
246 }
247 }
248
249 // Close select tag.
250 $html .= '>';
251
252 // If any prepended options exist, add them now.
253 if ( $prepend_options ) {
254 foreach ( $prepend_options as $value => $label ) {
255 $html .= sprintf(
256 '<option value="%s" data-preserve-on-refresh="1"%s>%s</option>',
257 esc_attr( $value ),
258 selected( $selected_option, $value, false ),
259 esc_attr( $label )
260 );
261 }
262 }
263
264 // Iterate through resources, if they exist, building <option> elements.
265 if ( $forms ) {
266 foreach ( $forms as $form ) {
267 // Legacy forms don't include a `format` key, so define them as inline.
268 $html .= sprintf(
269 '<option value="%s"%s>%s [%s]</option>',
270 esc_attr( $form['id'] ),
271 selected( $selected_option, $form['id'], false ),
272 esc_attr( $form['name'] ),
273 ( ! empty( $form['format'] ) ? esc_attr( $form['format'] ) : 'inline' )
274 );
275 }
276 }
277
278 // Close select.
279 $html .= '</select>';
280
281 // If no description is provided, return the select field now.
282 if ( ! $description ) {
283 return $html;
284 }
285
286 // Append description before returning field.
287 if ( ! is_array( $description ) ) {
288 return $html . '<p class="description">' . $description . '</p>';
289 }
290
291 // Return description lines in a paragraph, using breaklines for each description entry in the array.
292 return $html . '<p class="description">' . implode( '<br />', $description ) . '</p>';
293
294 }
295
296 /**
297 * Returns a <select> field populated with the resources, based on the given parameters,
298 * that supports multiple selection.
299 *
300 * @since 2.6.9
301 *
302 * @param array $forms Forms.
303 * @param string $name Name.
304 * @param string $id ID.
305 * @param bool|array $css_classes <select> CSS class(es).
306 * @param array $selected_options <option> values to mark as selected.
307 * @param bool|array $prepend_options <option> elements to prepend before resources.
308 * @param bool|array $attributes <select> attributes.
309 * @param bool|string|array $description Description.
310 * @return string HTML Select Field
311 */
312 private function get_multi_select_field( $forms, $name, $id, $css_classes, $selected_options = array(), $prepend_options = false, $attributes = false, $description = false ) {
313
314 $html = sprintf(
315 '<select name="%s[]" id="%s" class="%s" multiple',
316 esc_attr( $name ),
317 esc_attr( $id ),
318 esc_attr( ( is_array( $css_classes ) ? implode( ' ', $css_classes ) : '' ) )
319 );
320
321 // Append any attributes.
322 if ( $attributes ) {
323 foreach ( $attributes as $key => $value ) {
324 $html .= sprintf(
325 ' %s="%s"',
326 esc_attr( $key ),
327 esc_attr( $value )
328 );
329 }
330 }
331
332 // Close select tag.
333 $html .= '>';
334
335 // If any prepended options exist, add them now.
336 if ( $prepend_options ) {
337 foreach ( $prepend_options as $value => $label ) {
338 $html .= sprintf(
339 '<option value="%s" data-preserve-on-refresh="1"%s>%s</option>',
340 esc_attr( $value ),
341 ( in_array( $value, $selected_options, true ) ? ' selected' : '' ),
342 esc_attr( $label )
343 );
344 }
345 }
346
347 // Iterate through resources, if they exist, building <option> elements.
348 if ( $forms ) {
349 foreach ( $forms as $form ) {
350 // Legacy forms don't include a `format` key, so define them as inline.
351 $html .= sprintf(
352 '<option value="%s"%s>%s [%s]</option>',
353 esc_attr( $form['id'] ),
354 ( in_array( $form['id'], $selected_options, true ) ? ' selected' : '' ),
355 esc_attr( $form['name'] ),
356 ( ! empty( $form['format'] ) ? esc_attr( $form['format'] ) : 'inline' )
357 );
358 }
359 }
360
361 // Close select.
362 $html .= '</select>';
363
364 // If no description is provided, return the select field now.
365 if ( ! $description ) {
366 return $html;
367 }
368
369 // Append description before returning field.
370 if ( ! is_array( $description ) ) {
371 return $html . '<p class="description">' . $description . '</p>';
372 }
373
374 // Return description lines in a paragraph, using breaklines for each description entry in the array.
375 return $html . '<p class="description">' . implode( '<br />', $description ) . '</p>';
376
377 }
378
379 /**
380 * Returns the HTML/JS markup for the given Form ID.
381 *
382 * Legacy Forms will return HTML.
383 * Current Forms will return a <script> embed string.
384 *
385 * @since 1.9.6
386 *
387 * @param int $id Form ID.
388 * @return WP_Error|string
389 */
390 public function get_html( $id ) {
391
392 // Cast ID to integer.
393 $id = absint( $id );
394
395 // Bail if the resources are a WP_Error.
396 if ( is_wp_error( $this->resources ) ) {
397 return $this->resources;
398 }
399
400 // Bail if the resource doesn't exist.
401 if ( ! isset( $this->resources[ $id ] ) ) {
402 return new WP_Error(
403 'convertkit_resource_forms_get_html',
404 sprintf(
405 /* translators: ConvertKit Form ID */
406 __( 'Kit Form ID %s does not exist on Kit.', 'convertkit' ),
407 $id
408 )
409 );
410 }
411
412 // If no uid is present in the Form API data, this is a legacy form that's served by directly fetching the HTML
413 // from forms.kit.com.
414 if ( ! isset( $this->resources[ $id ]['uid'] ) ) {
415 // Initialize Settings.
416 $settings = new ConvertKit_Settings();
417
418 // Bail if no Access Token is specified in the Plugin Settings.
419 if ( ! $settings->has_access_token() ) {
420 return new WP_Error(
421 'convertkit_resource_forms_get_html',
422 __( 'Kit Legacy Form could not be fetched as no Access Token specified in Plugin Settings', 'convertkit' )
423 );
424 }
425
426 // Initialize the API.
427 $api = new ConvertKit_API_V4(
428 CONVERTKIT_OAUTH_CLIENT_ID,
429 CONVERTKIT_OAUTH_CLIENT_REDIRECT_URI,
430 $settings->get_access_token(),
431 $settings->get_refresh_token(),
432 $settings->debug_enabled(),
433 'output_form'
434 );
435
436 // Return Legacy Form HTML.
437 // We now call get_html() with the `embed_url` property, instead of get_form_html() with the `id` property,
438 // because `embed_url` includes the API Key.
439 return $api->get_html( $this->resources[ $id ]['embed_url'] );
440 }
441
442 // If the form's format is not an inline form, add the inline script before the closing </body> tag.
443 // This prevents a modal form's overlay being constrained by the WordPress Theme's styles,
444 // and accidentally embedding the same non-inline form twice, which would result in e.g. the same modal form
445 // displaying twice.
446 if ( $this->resources[ $id ]['format'] !== 'inline' ) {
447 add_filter(
448 'convertkit_output_scripts_footer',
449 function ( $scripts ) use ( $id ) {
450
451 $scripts[] = array(
452 'async' => true,
453 'data-uid' => $this->resources[ $id ]['uid'],
454 'src' => $this->resources[ $id ]['embed_js'],
455 );
456
457 return $scripts;
458
459 }
460 );
461
462 // Sanity check we're not in the WordPress Admin interface.
463 // Some third party REST API Plugins seem to load frontend Posts, which would result in a wp_die() as
464 // the output Plugin class (rightly) isn't initialized in the backend.
465 if ( is_admin() ) {
466 return '';
467 }
468
469 // Don't output the global non-inline form, if defined, because
470 // a non-inline form was specified at either Post/Page default level, Post/Page level
471 // or Post Category level.
472 // This prevents multiple non-inline forms loading.
473 remove_action( 'wp_footer', array( WP_ConvertKit()->get_class( 'output' ), 'output_global_non_inline_form' ), 1 );
474
475 // Don't return a script for output, as it'll be output in the site's footer.
476 return '';
477 }
478
479 // If here, return Form <script> embed now, as we want the inline form to display at this specific point of the content.
480
481 // Define script key-value pairs.
482 $script = array(
483 'async' => true,
484 'data-uid' => $this->resources[ $id ]['uid'],
485 'src' => $this->resources[ $id ]['embed_js'],
486 );
487
488 /**
489 * Filter the form <script> key/value pairs immediately before the script is output.
490 *
491 * @since 2.4.5
492 *
493 * @param array $script Form script key/value pairs to output as <script> tag.
494 */
495 $script = apply_filters( 'convertkit_resource_forms_output_script', $script );
496
497 // Build script output.
498 $output = '<script';
499 foreach ( $script as $attribute => $value ) {
500 // If the value is true, just output the attribute.
501 if ( $value === true ) {
502 $output .= ' ' . esc_attr( $attribute );
503 continue;
504 }
505
506 // Sanitize attribute and value.
507 $attribute = esc_attr( $attribute );
508 $value = ( $attribute === 'src' ? esc_url( $value ) : esc_attr( $value ) );
509
510 // Output the attribute and value.
511 $output .= ' ' . $attribute;
512
513 // Output the value, if it's not a blank string.
514 if ( strlen( $value ) > 0 ) {
515 $output .= '="' . $value . '"';
516 }
517 }
518 $output .= '></script>';
519
520 // Return script output.
521 return $output;
522
523 }
524
525 }
526