PluginProbe
Kit (formerly ConvertKit) – Email Newsletter, Email Marketing, Membership, Subscribers and Landing Pages / 2.3.2
Kit (formerly ConvertKit) – Email Newsletter, Email Marketing, Membership, Subscribers and Landing Pages v2.3.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 2.3.3 All 194 releases
convertkit / includes / class-convertkit-output-restrict-content.php

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

845 lines 27.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * ConvertKit Output Restrict Content class.
4 *
5 * @package ConvertKit
6 * @author ConvertKit
7 */
8
9 /**
10 * Restricts (or displays) a single Page, Post or Custom Post Type's content
11 * based on the Post's "Restrict Content" configuration.
12 *
13 * @since 2.1.0
14 */
15 class ConvertKit_Output_Restrict_Content {
16
17 /**
18 * Holds the success message to display on screen as a notification.
19 *
20 * @since 2.1.0
21 *
22 * @var bool|string
23 */
24 private $success = false;
25
26 /**
27 * Holds the WP_Error object if an API call / authentication failed,
28 * to display on screen as a notification.
29 *
30 * @since 2.1.0
31 *
32 * @var bool|WP_Error
33 */
34 private $error = false;
35
36 /**
37 * Holds the ConvertKit Plugin Settings class
38 *
39 * @since 2.1.0
40 *
41 * @var bool|ConvertKit_Settings
42 */
43 private $settings = false;
44
45 /**
46 * Holds the ConvertKit Restrict Content Settings class
47 *
48 * @since 2.1.0
49 *
50 * @var bool|ConvertKit_Settings_Restrict_Content
51 */
52 private $restrict_content_settings = false;
53
54 /**
55 * Holds the ConvertKit Post Settings class
56 *
57 * @since 2.1.0
58 *
59 * @var bool|ConvertKit_Post
60 */
61 private $post_settings = false;
62
63 /**
64 * Holds the Post ID
65 *
66 * @since 2.1.0
67 *
68 * @var bool|int
69 */
70 private $post_id = false;
71
72 /**
73 * Holds the ConvertKit API class
74 *
75 * @since 2.1.0
76 *
77 * @var bool|ConvertKit_API
78 */
79 private $api = false;
80
81 /**
82 * Holds the token returned from calling the subscriber_authentication_send_code API endpoint.
83 *
84 * @since 2.1.0
85 *
86 * @var bool|string
87 */
88 private $token = false;
89
90 /**
91 * Constructor. Registers actions and filters to possibly limit output of a Page/Post/CPT's
92 * content on the frontend site.
93 *
94 * @since 2.1.0
95 */
96 public function __construct() {
97
98 // Initialize classes that will be used.
99 $this->settings = new ConvertKit_Settings();
100 $this->restrict_content_settings = new ConvertKit_Settings_Restrict_Content();
101
102 add_action( 'init', array( $this, 'maybe_run_subscriber_authentication' ), 1 );
103 add_action( 'init', array( $this, 'maybe_run_subscriber_verification' ), 2 );
104 add_filter( 'the_content', array( $this, 'maybe_restrict_content' ) );
105 add_filter( 'get_previous_post_where', array( $this, 'maybe_change_previous_post_where_clause' ), 10, 5 );
106 add_filter( 'get_next_post_where', array( $this, 'maybe_change_next_post_where_clause' ), 10, 5 );
107 add_filter( 'get_previous_post_sort', array( $this, 'maybe_change_previous_next_post_order_by_clause' ), 10, 3 );
108 add_filter( 'get_next_post_sort', array( $this, 'maybe_change_previous_next_post_order_by_clause' ), 10, 3 );
109
110 }
111
112 /**
113 * Checks if the request is a Restrict Content request with an email address.
114 * If so, calls the API depending on the Restrict Content resource that's required:
115 * - tag: subscribes the email address to the tag, storing the subscriber ID in a cookie and redirecting
116 * - product: calls the API to send the subscriber a magic link by email containing a code. See maybe_run_subscriber_verification()
117 * for logic once they click the link in the email or enter the code on screen.
118 *
119 * @since 2.1.0
120 */
121 public function maybe_run_subscriber_authentication() {
122
123 // Bail if no nonce was specified.
124 if ( ! array_key_exists( '_wpnonce', $_REQUEST ) ) {
125 return;
126 }
127
128 // Bail if the nonce failed validation.
129 if ( ! wp_verify_nonce( sanitize_key( $_REQUEST['_wpnonce'] ), 'convertkit_restrict_content_login' ) ) {
130 $this->error = new WP_Error( 'convertkit_output_restrict_content_error', __( 'Invalid nonce specified. Please try again.', 'convertkit' ) );
131 return;
132 }
133
134 // If the Plugin API keys have not been configured, we can't get this subscriber's ID by email.
135 if ( ! $this->settings->has_api_key_and_secret() ) {
136 return;
137 }
138
139 // Initialize the API.
140 $this->api = new ConvertKit_API( $this->settings->get_api_key(), $this->settings->get_api_secret(), $this->settings->debug_enabled() );
141
142 // Sanitize inputs.
143 $email = sanitize_text_field( $_REQUEST['convertkit_email'] );
144 $resource_type = sanitize_text_field( $_REQUEST['convertkit_resource_type'] );
145 $resource_id = absint( sanitize_text_field( $_REQUEST['convertkit_resource_id'] ) );
146
147 // Run subscriber authentication / subscription depending on the resource type.
148 switch ( $resource_type ) {
149 case 'product':
150 // Send email to subscriber with a link to authenticate they have access to the email address submitted.
151 $result = $this->api->subscriber_authentication_send_code(
152 $email,
153 $this->get_url()
154 );
155
156 // Bail if an error occured.
157 if ( is_wp_error( $result ) ) {
158 $this->error = $result;
159 return;
160 }
161
162 // Clear any existing subscriber ID cookie, as the authentication flow has started by sending the email.
163 $subscriber = new ConvertKit_Subscriber();
164 $subscriber->forget();
165
166 // Store the token so it's included in the subscriber code form.
167 $this->token = $result;
168
169 // Show a message telling the subscriber to check their email and click the link in the email.
170 $this->success = $this->restrict_content_settings->get_by_key( 'email_check_text' );
171 break;
172
173 case 'tag':
174 // Tag the subscriber.
175 $result = $this->api->tag_subscribe( $resource_id, $email );
176
177 // Bail if an error occured.
178 if ( is_wp_error( $result ) ) {
179 $this->error = $result;
180 return;
181 }
182
183 // Clear any existing subscriber ID cookie, as the authentication flow has started by sending the email.
184 $subscriber = new ConvertKit_Subscriber();
185 $subscriber->forget();
186
187 // Fetch the subscriber ID from the result.
188 $subscriber_id = $result['subscription']['subscriber']['id'];
189
190 // Store subscriber ID in cookie and redirect.
191 $this->store_subscriber_id_in_cookie_and_redirect( $subscriber_id );
192 break;
193
194 }
195
196 }
197
198 /**
199 * Checks if the request contains a token and subscriber_code i.e. the subscriber clicked
200 * the link in the email sent by the maybe_run_subscriber_authentication() function above.
201 *
202 * This calls the API to verify the token and subscriber code, which tells us that the email
203 * address supplied truly belongs to the user, and that we can safely trust their subscriber ID
204 * to be valid.
205 *
206 * @since 2.1.0
207 */
208 public function maybe_run_subscriber_verification() {
209
210 // phpcs:disable WordPress.Security.NonceVerification.Recommended
211 // Bail if the expected token and subscriber code is missing.
212 if ( ! array_key_exists( 'token', $_REQUEST ) ) {
213 return;
214 }
215 if ( ! array_key_exists( 'subscriber_code', $_REQUEST ) ) {
216 return;
217 }
218
219 // If the Plugin API keys have not been configured, we can't get this subscriber's ID by email.
220 if ( ! $this->settings->has_api_key_and_secret() ) {
221 return;
222 }
223
224 // Store the token so it's included in the subscriber code form if verification fails.
225 $this->token = sanitize_text_field( $_REQUEST['token'] );
226
227 // Initialize the API.
228 $this->api = new ConvertKit_API( $this->settings->get_api_key(), $this->settings->get_api_secret(), $this->settings->debug_enabled() );
229
230 // Verify the token and subscriber code.
231 $subscriber_id = $this->api->subscriber_authentication_verify(
232 sanitize_text_field( $_REQUEST['token'] ),
233 sanitize_text_field( $_REQUEST['subscriber_code'] )
234 );
235 // phpcs:enable
236
237 // Bail if an error occured.
238 if ( is_wp_error( $subscriber_id ) ) {
239 $this->error = $subscriber_id;
240 return;
241 }
242
243 // Store subscriber ID in cookie and redirect.
244 $this->store_subscriber_id_in_cookie_and_redirect( $subscriber_id );
245
246 }
247
248 /**
249 * Displays (or hides) content on a singular Page, Post or Custom Post Type's Content,
250 * depending on whether the visitor is an authenticated ConvertKit subscriber and has
251 * subscribed to the ConvertKit Product or Tag.
252 *
253 * @since 2.1.0
254 *
255 * @param string $content Post Content.
256 * @return string Post Content with content restricted/not restricted
257 */
258 public function maybe_restrict_content( $content ) {
259
260 // Bail if the Restrict Content setting is not enabled on this Page.
261 if ( ! $this->is_restricted_content() ) {
262 return $content;
263 }
264
265 // Get resource type (Product or Tag) that the visitor must be subscribed against to access this content.
266 $resource_type = $this->get_resource_type( $this->post_id );
267
268 // Return the Post Content, unedited, if the Resource Type is false.
269 if ( ! $resource_type ) {
270 return $content;
271 }
272
273 // Get resource ID (Product ID or Tag ID) that the visitor must be subscribed against to access this content.
274 $resource_id = $this->get_resource_id( $this->post_id );
275
276 // Return the full Post Content, unedited, if the Resource ID is false, as this means
277 // no restrict content setting has been defined for this Post.
278 if ( ! $resource_id ) {
279 return $content;
280 }
281
282 // Return if this request is after the user entered their email address,
283 // which means we're going through the authentication flow.
284 if ( $this->in_authentication_flow() ) {
285 return $this->restrict_content( $content, $resource_type, $resource_id );
286 }
287
288 // Get the subscriber ID, either from the request or an existing cookie.
289 $subscriber_id = $this->get_subscriber_id_from_request();
290
291 // If no subscriber ID exists, the visitor cannot view the content.
292 if ( ! $subscriber_id ) {
293 return $this->restrict_content( $content, $resource_type, $resource_id );
294 }
295
296 // If the subscriber is not subscribed to the product, restrict the content.
297 if ( ! $this->subscriber_has_access( $subscriber_id, $resource_type, $resource_id ) ) {
298 // Show an error before the call to action, to tell the subscriber why they still cannot
299 // view the content.
300 $this->error = new WP_Error(
301 'convertkit_restrict_content_subscriber_no_access',
302 esc_html( $this->restrict_content_settings->get_by_key( 'no_access_text' ) )
303 );
304
305 return $this->restrict_content( $content, $resource_type, $resource_id );
306 }
307
308 // If here, the subscriber has subscribed to the product.
309 // Show the full Post Content.
310 return $content;
311
312 }
313
314 /**
315 * Changes how WordPress' get_adjacent_post() function queries Pages, to determine what
316 * the previous Page link is when using the Previous navigation block on a Page that
317 * has the Restrict Content setting defined.
318 *
319 * By default, get_adjacent_post() will query by post_date, which we change to menu_order.
320 *
321 * @since 2.1.0
322 *
323 * @param string $where The `WHERE` clause in the SQL.
324 * @param bool $in_same_term Whether post should be in a same taxonomy term.
325 * @param array $excluded_terms Array of excluded term IDs.
326 * @param string $taxonomy Taxonomy. Used to identify the term used when `$in_same_term` is true.
327 * @param WP_Post $post WP_Post object.
328 * @return string Modified `WHERE` clause
329 */
330 public function maybe_change_previous_post_where_clause( $where, $in_same_term, $excluded_terms, $taxonomy, $post ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter
331
332 // Bail if the Restrict Content setting is not enabled on this Page.
333 if ( ! $this->is_restricted_content() ) {
334 return $where;
335 }
336
337 // Bail if the Page doesn't match the current Page being viewed, or has no parent Page.
338 if ( ! $this->has_parent_page( $post ) ) {
339 return $where;
340 }
341
342 // Build replacement where statement.
343 $new_where = 'p.post_parent = ' . $post->post_parent . ' AND p.menu_order < ' . $post->menu_order;
344
345 // Replace existing where statement with new statement.
346 $where = 'WHERE ' . $new_where . ' ' . substr( $where, strpos( $where, 'AND' ) );
347
348 // Return.
349 return $where;
350
351 }
352
353 /**
354 * Changes how WordPress' get_adjacent_post() function queries Pages, to determine what
355 * the next Page link is when using the Previous navigation block on a Page that
356 * has the Restrict Content setting defined.
357 *
358 * By default, get_adjacent_post() will query by post_date, which we change to menu_order.
359 *
360 * @since 2.1.0
361 *
362 * @param string $where The `WHERE` clause in the SQL.
363 * @param bool $in_same_term Whether post should be in a same taxonomy term.
364 * @param array $excluded_terms Array of excluded term IDs.
365 * @param string $taxonomy Taxonomy. Used to identify the term used when `$in_same_term` is true.
366 * @param WP_Post $post WP_Post object.
367 * @return string Modified `WHERE` clause
368 */
369 public function maybe_change_next_post_where_clause( $where, $in_same_term, $excluded_terms, $taxonomy, $post ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter
370
371 // Bail if the Restrict Content setting is not enabled on this Page.
372 if ( ! $this->is_restricted_content() ) {
373 return $where;
374 }
375
376 // Bail if the Page doesn't match the current Page being viewed, or has no parent Page.
377 if ( ! $this->has_parent_page( $post ) ) {
378 return $where;
379 }
380
381 // Build replacement where statement.
382 $new_where = 'p.post_parent = ' . $post->post_parent . ' AND p.menu_order > ' . $post->menu_order;
383
384 // Replace existing where statement with new statement.
385 $where = 'WHERE ' . $new_where . ' ' . substr( $where, strpos( $where, 'AND' ) );
386
387 // Return.
388 return $where;
389
390 }
391
392 /**
393 * Changes how WordPress' get_adjacent_post() function orders Pages, to determine what
394 * the next and previous Page links are when using Previous / Next navigation blocks
395 * on a Page that has the Restrict Content setting defined.
396 *
397 * By default, get_adjacent_post() will sort by Post Date, which we change to Page Order
398 * (called menu_order in WordPress).
399 *
400 * @since 2.1.0
401 *
402 * @param string $order_by SQL ORDER BY statement.
403 * @param WP_Post $post WordPress Post.
404 * @param string $order Order.
405 * @return string Modified SQL ORDER BY statement.
406 */
407 public function maybe_change_previous_next_post_order_by_clause( $order_by, $post, $order ) {
408
409 // Bail if the Restrict Content setting is not enabled on this Page.
410 if ( ! $this->is_restricted_content() ) {
411 return $order_by;
412 }
413
414 // Bail if the Page doesn't match the current Page being viewed, or has no parent Page.
415 if ( ! $this->has_parent_page( $post ) ) {
416 return $order_by;
417 }
418
419 // Order by Page order (menu_order), highest to lowest, instead of post_date.
420 return 'ORDER BY p.menu_order ' . $order . ' LIMIT 1';
421
422 }
423
424 /**
425 * Stores the given subscriber ID in the ck_subscriber_id cookie, and redirects
426 * to the current URL, removing any query parameters (such as tokens), and appending
427 * a ck-cache-bust query parameter to beat caching plugins.
428 *
429 * @since 2.3.2
430 *
431 * @param string|int $subscriber_id Subscriber ID (int if restrict by tag, signed subscriber id string if restrict by product).
432 */
433 private function store_subscriber_id_in_cookie_and_redirect( $subscriber_id ) {
434
435 // Store subscriber ID in cookie.
436 // We don't need to use validate_and_store_subscriber_id() as we just validated the subscriber via authentication above.
437 $subscriber = new ConvertKit_Subscriber();
438 $subscriber->set( $subscriber_id );
439
440 // We append a query parameter to the URL to prevent caching plugins and
441 // aggressive cache hosting configurations from serving a cached page, which would
442 // result in maybe_restrict_content() not showing an error message or permitting
443 // access to the content.
444 $url = add_query_arg(
445 array(
446 'ck-cache-bust' => microtime(),
447 ),
448 $this->get_url()
449 );
450
451 // Redirect to the Post without parameters.
452 // This will then run maybe_restrict_content() to get the subscriber's ID from the cookie,
453 // and determine if the content can be displayed.
454 wp_safe_redirect( $url );
455 exit;
456
457 }
458
459 /**
460 * Returns the URL for the current request, excluding any query parameters.
461 *
462 * @since 2.1.0
463 *
464 * @return string URL.
465 */
466 private function get_url() {
467
468 $url = wp_parse_url( get_site_url() . $_SERVER['REQUEST_URI'] );
469 return $url['scheme'] . '://' . $url['host'] . $url['path'];
470
471 }
472
473 /**
474 * Determines if the request is for a WordPress Page that has the Restrict Content
475 * setting defined.
476 *
477 * @since 2.1.0
478 *
479 * @return bool
480 */
481 private function is_restricted_content() {
482
483 // Bail if not a singular Post Type.
484 if ( ! is_singular() ) {
485 return false;
486 }
487
488 // If a Post ID is already defined in this class, this check has already been performed,
489 // and the Post's settings class has been initialized.
490 if ( $this->post_id ) {
491 return true;
492 }
493
494 // Get Post ID.
495 $this->post_id = get_the_ID();
496
497 // Initialize Settings and Post Setting classes.
498 $this->post_settings = new ConvertKit_Post( $this->post_id );
499
500 // If the Plugin API keys have not been configured, we can't determine the validity of this subscriber ID
501 // or which resource(s) they have access to.
502 if ( ! $this->settings->has_api_key_and_secret() ) {
503 return false;
504 }
505
506 // Return whether the Post's settings are set to restrict content.
507 return $this->post_settings->restrict_content_enabled();
508
509 }
510
511 /**
512 * Determines if the user entered a valid email address, and need to be prompted
513 * to enter a code sent to their email address.
514 *
515 * @since 2.1.0
516 *
517 * @return bool
518 */
519 private function in_authentication_flow() {
520
521 return ( $this->token !== false );
522
523 }
524
525 /**
526 * Checks if the given WordPress Page matches the Page ID viewed, and has a parent.
527 *
528 * @since 2.1.0
529 *
530 * @param WP_Post $post WordPress Post.
531 * @return bool Has parent page
532 */
533 private function has_parent_page( $post ) {
534
535 // Bail if the Page doesn't match the current Page being viewed.
536 // This prevents us accidentally interfering with other previous / next link queries, which shouldn't happen
537 // as we check if we're viewing a restricted content page above.
538 if ( $post->ID !== $this->post_id ) {
539 return false;
540 }
541
542 // Bail if the Page doesn't have a parent Page.
543 // We don't want to modify the default sort behaviour in this instance.
544 if ( $post->post_parent === 0 ) {
545 return false;
546 }
547
548 return true;
549
550 }
551
552 /**
553 * Get the Post's Restricted Content resource type.
554 *
555 * @since 2.1.0
556 *
557 * @param int $post_id Post ID.
558 * @return bool|string Resource Type (product).
559 */
560 private function get_resource_type( $post_id ) {
561
562 // Get resource type.
563 $resource_type = $this->post_settings->get_restrict_content_type();
564
565 /**
566 * Define the ConvertKit Resource Type that the visitor must be subscribed against
567 * to access this content, overriding the Post setting.
568 *
569 * Return false or an empty string to not restrict content.
570 *
571 * @since 2.1.0
572 *
573 * @param string $resource_type Resource Type (product)
574 * @param int $post_id Post ID
575 */
576 $resource_type = apply_filters( 'convertkit_output_restrict_content_get_resource_type', $resource_type, $post_id );
577
578 // If resource type is blank, set it to false.
579 if ( empty( $resource_type ) ) {
580 $resource_type = false;
581 }
582
583 // Return.
584 return $resource_type;
585
586 }
587
588 /**
589 * Get the Post's Restricted Content resource ID.
590 *
591 * @since 2.1.0
592 *
593 * @param int $post_id Post ID.
594 * @return int Resource ID (product ID).
595 */
596 private function get_resource_id( $post_id ) {
597
598 // Get resource ID.
599 $resource_id = $this->post_settings->get_restrict_content_id();
600
601 /**
602 * Define the ConvertKit Resource ID that the visitor must be subscribed against
603 * to access this content, overriding the Post setting.
604 *
605 * Return 0 to not restrict content.
606 *
607 * @since 2.1.0
608 *
609 * @param int $resource_id Resource ID
610 * @param int $post_id Post ID
611 */
612 $resource_id = apply_filters( 'convertkit_output_restrict_content_get_resource_id', $resource_id, $post_id );
613
614 // Return.
615 return $resource_id;
616
617 }
618
619 /**
620 * Determines if the given subscriber has an active subscription to
621 * the given resource and its ID.
622 *
623 * @since 2.1.0
624 *
625 * @param string|int $subscriber_id Signed Subscriber ID or Subscriber ID.
626 * @param string $resource_type Resource Type (product).
627 * @param int $resource_id Resource ID (Product ID).
628 * @return bool Can view restricted content
629 */
630 private function subscriber_has_access( $subscriber_id, $resource_type, $resource_id ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter
631
632 // Initialize the API.
633 $this->api = new ConvertKit_API( $this->settings->get_api_key(), $this->settings->get_api_secret(), $this->settings->debug_enabled() );
634
635 // Depending on the resource type, determine if the subscriber has access to it.
636 // This is deliberately a switch statement, because we will likely add in support
637 // for restrict by tag and form later.
638 switch ( $resource_type ) {
639 case 'product':
640 // Get products that the subscriber has access to.
641 $result = $this->api->profile( $subscriber_id );
642
643 // If an error occured, the subscriber ID is invalid.
644 if ( is_wp_error( $result ) ) {
645 return false;
646 }
647
648 // If no products exist, there's no access.
649 if ( ! $result['products'] || ! count( $result['products'] ) ) {
650 return false;
651 }
652
653 // Return if the subscriber is not subscribed to the product.
654 if ( ! in_array( absint( $resource_id ), $result['products'], true ) ) {
655 return false;
656 }
657
658 // If here, the subscriber is subscribed to the product.
659 return true;
660
661 case 'tag':
662 // Get tags that the subscriber has been assigned.
663 $tags = $this->api->get_subscriber_tags( $subscriber_id );
664
665 // If an error occured, the subscriber ID is invalid.
666 if ( is_wp_error( $tags ) ) {
667 return false;
668 }
669
670 // If no tags exist, there's no access.
671 if ( ! count( $tags ) ) {
672 return false;
673 }
674
675 // Iterate through the subscriber's tags to see if they have the required tag.
676 foreach ( $tags as $tag ) {
677 if ( $tag['id'] === absint( $resource_id ) ) {
678 // Subscriber has the required tag assigned to them - grant access.
679 return true;
680 }
681 }
682
683 // If here, the subscriber does not have the tag.
684 return false;
685 }
686
687 // If here, the subscriber does not have access.
688 return false;
689
690 }
691
692 /**
693 * Gets the subscriber ID from the request (either the cookie or the URL).
694 *
695 * @since 2.1.0
696 *
697 * @return int|string Subscriber ID or Signed ID
698 */
699 public function get_subscriber_id_from_request() {
700
701 // Use ConvertKit_Subscriber class to fetch and validate the subscriber ID.
702 $subscriber = new ConvertKit_Subscriber();
703 $subscriber_id = $subscriber->get_subscriber_id();
704
705 // If an error occured, the subscriber ID in the request/cookie is not a valid subscriber.
706 if ( is_wp_error( $subscriber_id ) ) {
707 return 0;
708 }
709
710 return $subscriber_id;
711
712 }
713
714 /**
715 * Restrict the given Post Content by showing a preview of the content, and appending
716 * the call to action to subscribe or authenticate.
717 *
718 * @since 2.1.0
719 *
720 * @param string $content Post Content.
721 * @param string $resource_type Resource Type (product).
722 * @param int $resource_id Resource ID (Product ID).
723 * @return string Post Content preview with call to action
724 */
725 private function restrict_content( $content, $resource_type, $resource_id ) {
726
727 return $this->get_content_preview( $content ) . $this->get_call_to_action( $this->post_id, $resource_type, $resource_id );
728
729 }
730
731 /**
732 * Returns a preview of the given content for visitors that don't have access to restricted content.
733 *
734 * The preview is determined by:
735 * - A single <!--more--> tag being placed between WordPress paragraphs when using the Classic Editor.
736 * Content before the tag will be returned as the preview, unless 'noteaser' is enabled.
737 * - A single 'Read More' block being placed between WordPress blocks when using the Gutenberg Editor.
738 * Content before the Read More block will be returned as the preview, unless 'Hide th excerpt
739 * on the full content page' is enabled.
740 *
741 * No preview content is returned if the above conditions are not met.
742 *
743 * @since 2.1.0
744 *
745 * @param string $content Post Content.
746 * @return string Post Content Preview.
747 */
748 private function get_content_preview( $content ) {
749
750 global $post;
751
752 // Check if the content contains a <!--more--> tag, which the editor might have placed
753 // in the content through WordPress' Classic Editor.
754 $content_breakdown = get_extended( $content );
755
756 // If the <!-- more --> tag exists, the 'extended' key will contain the restricted content.
757 if ( ! empty( $content_breakdown['extended'] ) ) {
758 // Return the preview content.
759 return $content_breakdown['main'];
760 }
761
762 // Check if the content contains a 'Read More' block, which the editor might have placed
763 // in the content through the Gutenberg Editor.
764 $block_editor_tag = '<span id="more-' . $post->ID . '"></span>';
765 if ( strpos( $content, $block_editor_tag ) !== false ) {
766 // Split content into an array by the tag.
767 $content_breakdown = explode( $block_editor_tag, $content );
768
769 // Return the content before the tag.
770 // If noteaser is enabled, this will correctly be blank.
771 return $content_breakdown[0];
772 }
773
774 // If here, there is no preview content available. Don't return any content.
775 return '';
776
777 }
778
779 /**
780 * Returns the HTML output for the call to action for visitors not subscribed to the required
781 * resource type and ID.
782 *
783 * @since 2.1.0
784 *
785 * @param int $post_id Post ID.
786 * @param string $resource_type Resource Type (product).
787 * @param int $resource_id Resource ID (Product ID).
788 * @return string HTML
789 */
790 private function get_call_to_action( $post_id, $resource_type, $resource_id ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter
791
792 // Read success and error notices from this class.
793 $success = $this->success;
794 $error = $this->error;
795
796 // Only load styles if the Disable CSS option is off.
797 if ( ! $this->settings->css_disabled() ) {
798 // Enqueue styles.
799 wp_enqueue_style( 'convertkit-restrict-content', CONVERTKIT_PLUGIN_URL . 'resources/frontend/css/restrict-content.css', array(), CONVERTKIT_PLUGIN_VERSION );
800 }
801
802 // This is deliberately a switch statement, because we will likely add in support
803 // for restrict by tag and form later.
804 switch ( $resource_type ) {
805 case 'product':
806 // Output product code form if this request is after the user entered their email address,
807 // which means we're going through the authentication flow.
808 if ( $this->in_authentication_flow() ) { // phpcs:ignore WordPress.Security.NonceVerification
809 ob_start();
810 include CONVERTKIT_PLUGIN_PATH . '/views/frontend/restrict-content/product-code.php';
811 return trim( ob_get_clean() );
812 }
813
814 // Output product restricted message and email form.
815 // Get Product.
816 $products = new ConvertKit_Resource_Products( 'restrict_content' );
817 $product = $products->get_by_id( $resource_id );
818
819 // Get commerce.js URL and enqueue.
820 $url = $products->get_commerce_js_url();
821 if ( $url ) {
822 wp_enqueue_script( 'convertkit-commerce', $url, array(), CONVERTKIT_PLUGIN_VERSION, true );
823 }
824
825 // Output.
826 ob_start();
827 $button = $products->get_html( $resource_id, $this->restrict_content_settings->get_by_key( 'subscribe_button_label' ) );
828 include CONVERTKIT_PLUGIN_PATH . '/views/frontend/restrict-content/product.php';
829 return trim( ob_get_clean() );
830
831 case 'tag':
832 // Output.
833 ob_start();
834 include CONVERTKIT_PLUGIN_PATH . '/views/frontend/restrict-content/tag.php';
835 return trim( ob_get_clean() );
836
837 default:
838 return '';
839
840 }
841
842 }
843
844 }
845