PluginProbe
Kit (formerly ConvertKit) – Email Newsletter, Email Marketing, Membership, Subscribers and Landing Pages / 2.4.7
Kit (formerly ConvertKit) – Email Newsletter, Email Marketing, Membership, Subscribers and Landing Pages v2.4.7
3.4.2 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 All 195 releases
convertkit / includes / class-convertkit-output.php

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

555 lines 15.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * ConvertKit Output class.
4 *
5 * @package ConvertKit
6 * @author ConvertKit
7 */
8
9 /**
10 * Outputs Forms and Landing Pages on the frontend web site, based on
11 * the Post and Plugin's configuration.
12 *
13 * @since 1.9.6
14 */
15 class ConvertKit_Output {
16
17 /**
18 * Holds the ConvertKit Subscriber ID.
19 *
20 * @since 2.0.6
21 *
22 * @var int|string
23 */
24 private $subscriber_id = 0;
25
26 /**
27 * Holds the ConvertKit Plugin Settings class
28 *
29 * @since 1.9.6
30 *
31 * @var bool|ConvertKit_Settings
32 */
33 private $settings = false;
34
35 /**
36 * Holds the ConvertKit Post Settings class
37 *
38 * @since 1.9.6
39 *
40 * @var bool|ConvertKit_Post
41 */
42 private $post_settings = false;
43
44 /**
45 * Holds the available ConvertKit Forms
46 *
47 * @since 1.9.6
48 *
49 * @var bool|ConvertKit_Resource_Forms
50 */
51 private $forms = false;
52
53 /**
54 * Holds the available ConvertKit Landing Pages
55 *
56 * @since 1.9.6
57 *
58 * @var bool|ConvertKit_Resource_Landing_Pages
59 */
60 private $landing_pages = false;
61
62 /**
63 * Constructor. Registers actions and filters to output ConvertKit Forms and Landing Pages
64 * on the frontend web site.
65 *
66 * @since 1.9.6
67 */
68 public function __construct() {
69
70 add_action( 'init', array( $this, 'get_subscriber_id_from_request' ), 1 );
71 add_action( 'template_redirect', array( $this, 'output_form' ) );
72 add_action( 'template_redirect', array( $this, 'page_takeover' ) );
73 add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
74 add_filter( 'the_content', array( $this, 'append_form_to_content' ) );
75 add_action( 'wp_footer', array( $this, 'output_global_non_inline_form' ), 1 );
76 add_action( 'wp_footer', array( $this, 'output_scripts_footer' ) );
77
78 }
79
80 /**
81 * Runs the `convertkit_output_output_form` action for singular Post Types that don't use the_content()
82 * or apply_filters( 'the_content' ) to output a ConvertKit Form.
83 *
84 * @since 1.9.6
85 */
86 public function output_form() {
87
88 /**
89 * Outputs a ConvertKit Form on singular Post Types that don't use the_content()
90 * or apply_filters( 'the_content' ).
91 *
92 * @since 1.9.6
93 *
94 * @return string Post Content with Form Appended, if applicable
95 */
96 do_action( 'convertkit_output_output_form' );
97
98 }
99
100 /**
101 * Outputs a ConvertKit Landing Page if configured, replacing all output for the singular Post Type.
102 *
103 * @since 1.9.6
104 */
105 public function page_takeover() {
106
107 $queried_object = get_queried_object();
108
109 // Bail if the queried object cannot be inspected.
110 if ( ! isset( $queried_object->post_type ) ) {
111 return;
112 }
113
114 // Get Post ID.
115 $post_id = $queried_object->ID;
116
117 // Bail if the queried object isn't a supported Post Type for Landing Pages.
118 if ( $queried_object->post_type !== 'page' ) {
119 return;
120 }
121
122 // Get ConvertKit Post's Settings, if they have not yet been loaded.
123 if ( ! $this->post_settings ) {
124 $this->post_settings = new ConvertKit_Post( $post_id );
125 }
126
127 // Get Landing Page ID.
128 $landing_page_id = $this->post_settings->get_landing_page();
129
130 /**
131 * Define the ConvertKit Landing Page ID to display for the given Post ID,
132 * overriding the Post settings.
133 *
134 * Return false to not display any ConvertKit Landing Page.
135 *
136 * @since 1.9.6
137 *
138 * @param int $landing_page_id Landing Page ID
139 * @param int $post_id Post ID
140 */
141 $landing_page_id = apply_filters( 'convertkit_output_page_takeover_landing_page_id', $landing_page_id, $post_id );
142
143 // Bail if no Landing Page is configured to be output.
144 if ( empty( $landing_page_id ) ) {
145 return;
146 }
147
148 // Get available ConvertKit Landing Pages, if they have not yet been loaded.
149 if ( ! $this->landing_pages ) {
150 $this->landing_pages = new ConvertKit_Resource_Landing_Pages( 'output_landing_page' );
151 }
152
153 // Get Landing Page.
154 $landing_page = $this->landing_pages->get_html( $this->post_settings->get_landing_page() );
155
156 // Bail if an error occured.
157 if ( is_wp_error( $landing_page ) ) {
158 return;
159 }
160
161 // Replace the favicon with the WordPress site's favicon, if specified.
162 $landing_page = $this->landing_pages->replace_favicon( $landing_page );
163
164 /**
165 * Perform any actions immediately prior to outputting the Landing Page.
166 *
167 * Caching and minification Plugins may need to hook here to prevent
168 * CSS / JS minification and lazy loading images, which can interfere
169 * with Landing Pages.
170 *
171 * @since 2.4.4
172 *
173 * @param string $landing_page ConvertKit Landing Page HTML.
174 * @param int $landing_page_id ConvertKit Landing Page ID.
175 * @param int $post_id WordPress Page ID.
176 */
177 do_action( 'convertkit_output_landing_page_before', $landing_page, $landing_page_id, $post_id );
178
179 // Output Landing Page.
180 // Output is supplied from ConvertKit's API, which is already sanitized.
181 echo $landing_page; // phpcs:ignore WordPress.Security.EscapeOutput
182 exit;
183
184 }
185
186 /**
187 * Appends a form to the singular Page, Post or Custom Post Type's Content.
188 *
189 * @param string $content Post Content.
190 * @return string Post Content with Form Appended, if applicable
191 */
192 public function append_form_to_content( $content ) {
193
194 // Bail if not a singular Post Type supported by ConvertKit.
195 if ( ! is_singular( convertkit_get_supported_post_types() ) ) {
196 return $content;
197 }
198
199 // Get Post ID and ConvertKit Form ID for the Post.
200 $post_id = get_the_ID();
201 $form_id = $this->get_post_form_id( $post_id );
202
203 /**
204 * Define the ConvertKit Form ID to display for the given Post ID,
205 * overriding the Post, Category or Plugin settings.
206 *
207 * Return false to not display any ConvertKit Form.
208 *
209 * @since 1.9.6
210 *
211 * @param bool|int $form_id Form ID
212 * @param int $post_id Post ID
213 */
214 $form_id = apply_filters( 'convertkit_output_append_form_to_content_form_id', $form_id, $post_id );
215
216 // Return the Post Content, unedited, if the Form ID is false or zero.
217 if ( ! $form_id ) {
218 return $content;
219 }
220
221 // Get available ConvertKit Forms, if they have not yet been loaded.
222 if ( ! $this->forms ) {
223 $this->forms = new ConvertKit_Resource_Forms( 'output_form' );
224 }
225
226 // Get Form HTML.
227 $form = $this->forms->get_html( $form_id );
228
229 // If an error occured, it could be because the specified Form ID for the Post either:
230 // - belongs to another ConvertKit account (i.e. API credentials were changed in the Plugin, but this Post's specified Form was not changed), or
231 // - the form was deleted from the ConvertKit account.
232 // Attempt to fallback to the default form for this Post Type.
233 if ( is_wp_error( $form ) ) {
234 if ( $this->settings->debug_enabled() ) {
235 $content .= '<!-- ConvertKit append_form_to_content(): ' . $form->get_error_message() . ' Attempting fallback to Default Form. -->';
236 }
237
238 // Get Default Form ID for this Post's Type.
239 $form_id = $this->settings->get_default_form( get_post_type( $post_id ) );
240
241 // If no Default Form is specified, just return the Post Content, unedited.
242 if ( ! $form_id ) {
243 if ( $this->settings->debug_enabled() ) {
244 $content .= '<!-- ConvertKit append_form_to_content(): No Default Form exists as a fallback. -->';
245 }
246
247 return $content;
248 }
249
250 // Get Form HTML.
251 $form = $this->forms->get_html( $form_id );
252
253 // If an error occured again, the default form doesn't exist in this ConvertKit account.
254 // Just return the Post Content, unedited.
255 if ( is_wp_error( $form ) ) {
256 if ( $this->settings->debug_enabled() ) {
257 $content .= '<!-- ConvertKit append_form_to_content(): Default Form: ' . $form->get_error_message() . ' -->';
258 }
259
260 return $content;
261 }
262 }
263
264 // If here, we have a ConvertKit Form.
265 // Append form to Post's Content.
266 $content = $content .= $form;
267
268 /**
269 * Filter the Post's Content, which includes a ConvertKit Form, immediately before it is output.
270 *
271 * @since 1.9.6
272 *
273 * @param string $content Post Content
274 * @param string $form ConvertKit Form HTML
275 * @param int $post_id Post ID
276 * @param int $form_id ConvertKit Form ID
277 */
278 $content = apply_filters( 'convertkit_frontend_append_form', $content, $form, $post_id, $form_id );
279
280 return $content;
281
282 }
283
284 /**
285 * Returns the Post, Category or Plugin ConvertKit Form ID for the given Post.
286 *
287 * If the Post specifies a form to use, returns that Form ID.
288 * If the Post uses the 'Default' setting, and an assigned Category has a Form ID, uses the Category's Form ID.
289 * Otherwise falls back to the Plugin's Default Form ID (if any).
290 *
291 * @since 1.9.6
292 *
293 * @param int $post_id Post ID.
294 * @return bool|string|int false|'default'|Form ID
295 */
296 private function get_post_form_id( $post_id ) {
297
298 // Get Settings, if they have not yet been loaded.
299 if ( ! $this->settings ) {
300 $this->settings = new ConvertKit_Settings();
301 }
302
303 // Get ConvertKit Post's Settings, if they have not yet been loaded.
304 if ( ! $this->post_settings ) {
305 $this->post_settings = new ConvertKit_Post( $post_id );
306 }
307
308 // If the Post specifies a Form to use, return its ID now.
309 if ( $this->post_settings->has_form() ) {
310 return $this->post_settings->get_form();
311 }
312
313 // If the Post specifies that no Form should be used, return false.
314 if ( $this->post_settings->uses_no_form() ) {
315 return false;
316 }
317
318 // Sanity check that the Post uses the Default Form setting, which should be the case
319 // because the above conditions were not met.
320 if ( ! $this->post_settings->uses_default_form() ) {
321 return false;
322 }
323
324 // Get Post's Categories.
325 $categories = wp_get_post_categories(
326 $post_id,
327 array(
328 'fields' => 'ids',
329 )
330 );
331
332 // If no Categories exist, use the Default Form.
333 if ( ! is_array( $categories ) || ! count( $categories ) ) {
334 // Get Post Type.
335 return $this->settings->get_default_form( get_post_type( $post_id ) );
336 }
337
338 /**
339 * Iterate through Categories in reverse order.
340 * This honors the behaviour < 1.9.6, which states that if multiple Categories each have a Form.
341 * assigned, the last Category with a Form in the wp_get_post_categories() call will be used.
342 */
343 $categories = array_reverse( $categories );
344 foreach ( $categories as $term_id ) {
345 // Load Term Settings.
346 $term_settings = new ConvertKit_Term( $term_id );
347
348 // If a Form ID exists, return it now.
349 if ( $term_settings->has_form() ) {
350 return $term_settings->get_form();
351 }
352 }
353
354 // If here, use the Plugin's Default Form.
355 return $this->settings->get_default_form( get_post_type( $post_id ) );
356
357 }
358
359 /**
360 * Enqueue scripts.
361 *
362 * @since 1.9.6
363 */
364 public function enqueue_scripts() {
365
366 // Get Post.
367 $post = get_post();
368
369 // Bail if no Post could be fetched.
370 if ( ! $post ) {
371 return;
372 }
373
374 // Get ConvertKit Settings and Post's Settings.
375 $settings = new ConvertKit_Settings();
376 $convertkit_post = new ConvertKit_Post( $post->ID );
377
378 // Register scripts that we might use.
379 wp_register_script(
380 'convertkit-js',
381 CONVERTKIT_PLUGIN_URL . 'resources/frontend/js/convertkit.js',
382 array(),
383 CONVERTKIT_PLUGIN_VERSION,
384 true
385 );
386 wp_localize_script(
387 'convertkit-js',
388 'convertkit',
389 array(
390 'ajaxurl' => admin_url( 'admin-ajax.php' ),
391 'debug' => $settings->debug_enabled(),
392 'nonce' => wp_create_nonce( 'convertkit' ),
393 'subscriber_id' => $this->subscriber_id,
394 'tag' => ( ( is_singular() && $convertkit_post->has_tag() ) ? $convertkit_post->get_tag() : false ),
395 'post_id' => $post->ID,
396 )
397 );
398
399 // Bail if the no scripts setting is enabled.
400 if ( $settings->scripts_disabled() ) {
401 return;
402 }
403
404 // Enqueue.
405 wp_enqueue_script( 'convertkit-js' );
406
407 }
408
409 /**
410 * Gets the subscriber ID from the request (either the cookie or the URL).
411 *
412 * @since 1.9.6
413 */
414 public function get_subscriber_id_from_request() {
415
416 // Use ConvertKit_Subscriber class to fetch and validate the subscriber ID.
417 $subscriber = new ConvertKit_Subscriber();
418 $subscriber_id = $subscriber->get_subscriber_id();
419
420 // If an error occured, the subscriber ID in the request/cookie is not a valid subscriber.
421 if ( is_wp_error( $subscriber_id ) ) {
422 return;
423 }
424
425 $this->subscriber_id = $subscriber_id;
426
427 }
428
429 /**
430 * Outputs a non-inline form if defined in the Plugin's settings >
431 * Default Non-Inline Form (Global) setting.
432 *
433 * @since 2.3.3
434 */
435 public function output_global_non_inline_form() {
436
437 // Get Settings, if they have not yet been loaded.
438 if ( ! $this->settings ) {
439 $this->settings = new ConvertKit_Settings();
440 }
441
442 // Bail if no non-inline form setting is specified.
443 if ( ! $this->settings->has_non_inline_form() ) {
444 return;
445 }
446
447 // Get form.
448 $convertkit_forms = new ConvertKit_Resource_Forms();
449 $form = $convertkit_forms->get_by_id( (int) $this->settings->get_non_inline_form() );
450
451 // Bail if the Form doesn't exist (this shouldn't happen, but you never know).
452 if ( ! $form ) {
453 return;
454 }
455
456 // Add the form to the scripts array so it is included in the output.
457 add_filter(
458 'convertkit_output_scripts_footer',
459 function ( $scripts ) use ( $form ) {
460
461 $scripts[] = array(
462 'async' => true,
463 'data-uid' => $form['uid'],
464 'src' => $form['embed_js'],
465 );
466
467 return $scripts;
468
469 }
470 );
471
472 }
473
474 /**
475 * Outputs any JS <script> tags registered with the convertkit_output_scripts_footer
476 * filter
477 *
478 * @since 2.1.4
479 */
480 public function output_scripts_footer() {
481
482 // Define array of scripts.
483 $scripts = array();
484
485 /**
486 * Define an array of scripts to output in the footer of the WordPress site.
487 *
488 * @since 2.1.4
489 *
490 * @param array $scripts Scripts.
491 */
492 $scripts = apply_filters( 'convertkit_output_scripts_footer', $scripts );
493
494 // Bail if no scripts exist.
495 if ( ! count( $scripts ) ) {
496 return;
497 }
498
499 // Define array to store <script> outputs.
500 $output_scripts = array();
501
502 // Iterate through scripts, building the <script> tag for each.
503 foreach ( $scripts as $script ) {
504 /**
505 * Filter the form <script> key/value pairs immediately before the script is output.
506 *
507 * @since 2.4.5
508 *
509 * @param array $script Form script key/value pairs to output as <script> tag.
510 */
511 $script = apply_filters( 'convertkit_output_script_footer', $script );
512
513 // Build output.
514 $output = '<script';
515 foreach ( $script as $attribute => $value ) {
516 // If the value is true, just output the attribute.
517 if ( $value === true ) {
518 $output .= ' ' . esc_attr( $attribute );
519 continue;
520 }
521
522 // Sanitize attribute and value.
523 $attribute = esc_attr( $attribute );
524 $value = ( $attribute === 'src' ? esc_url( $value ) : esc_attr( $value ) );
525
526 // Output the attribute and value.
527 $output .= ' ' . $attribute;
528
529 // Output the value, if it's not a blank string.
530 if ( strlen( $value ) > 0 ) {
531 $output .= '="' . $value . '"';
532 }
533 }
534
535 $output .= '></script>';
536
537 // Add to array.
538 $output_scripts[] = $output;
539 }
540
541 // Remove duplicate scripts.
542 // This prevents the same non-inline form displaying twice. For example, if a modal form is specified both
543 // in the Page's settings and the Form block, the user would see the same modal form displayed twice
544 // because the script would be output twice.
545 $output_scripts = array_unique( $output_scripts );
546
547 // Output scripts.
548 foreach ( $output_scripts as $output_script ) {
549 echo $output_script . "\n"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
550 }
551
552 }
553
554 }
555