PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3-a.5
Jetpack – WP Security, Backup, Speed, & Growth v16.3-a.5
16.3-beta 16.3-a.5 16.3-a.7 16.3-a.3 16.3-a.1 16.2 16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 All 507 releases
jetpack / jetpack_vendor / automattic / jetpack-seo / src / class-content-gate.php

class-content-gate.php in Jetpack – WP Security, Backup, Speed, & Growth 16.3-a.5, at jetpack_vendor/automattic/jetpack-seo/src/class-content-gate.php

166 lines 4.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Whether a post's body is withheld from the public by a content gate.
4 *
5 * @package automattic/jetpack-seo-package
6 */
7
8 namespace Automattic\Jetpack\SEO;
9
10 /**
11 * Whether a post's body is withheld from an anonymous reader.
12 *
13 * Crawler-facing surfaces (meta descriptions, Open Graph tags, JSON-LD, llms.txt)
14 * read the raw `post_content` column, so they never pass through the `the_content`
15 * chain where the subscriptions paywall is installed and must ask this themselves.
16 *
17 * Visitor-independent by design: those surfaces are cached and shared, so their
18 * output must not vary by who fetched them.
19 *
20 * @since 0.9.2
21 */
22 class Content_Gate {
23
24 /**
25 * Post meta set when a post contains paid content.
26 *
27 * @var string
28 */
29 const META_CONTAINS_PAID_CONTENT = '_jetpack_memberships_contains_paid_content';
30
31 /**
32 * Post meta set when a post contains paywalled content.
33 *
34 * @var string
35 */
36 const META_CONTAINS_PAYWALLED_CONTENT = '_jetpack_memberships_contains_paywalled_content';
37
38 /**
39 * Post meta holding the post-level Newsletter access setting.
40 *
41 * @var string
42 */
43 const META_NEWSLETTER_ACCESS = '_jetpack_newsletter_access';
44
45 /**
46 * The access level that means "no gate".
47 *
48 * @var string
49 */
50 const ACCESS_LEVEL_EVERYBODY = 'everybody';
51
52 /**
53 * The block that splits a post into a public teaser and a gated body.
54 *
55 * @var string
56 */
57 const BLOCK_PAYWALL = 'jetpack/paywall';
58
59 /**
60 * The namespace whose blocks hide their own content at render time.
61 *
62 * @var string
63 */
64 const BLOCK_NAMESPACE_PREMIUM_CONTENT = 'premium-content';
65
66 /**
67 * The block that wraps gated content.
68 *
69 * @var string
70 */
71 const BLOCK_PREMIUM_CONTENT = self::BLOCK_NAMESPACE_PREMIUM_CONTENT . '/container';
72
73 /**
74 * Whether this post's body is withheld from an anonymous reader.
75 *
76 * Any Newsletter access level other than "everybody" counts as gated, even one
77 * the front end would not enforce (an unknown value, or the Subscriptions
78 * module switched off after the level was set). Erring that way withholds a
79 * summary; erring the other way publishes a paywalled body.
80 *
81 * @since 0.9.2
82 *
83 * @param \WP_Post|int|null $post Post, post ID, or null for the global post.
84 * @return bool True when the body must not be published.
85 */
86 public static function is_gated( $post = null ) {
87 // A WP_Post is taken as given: get_post() would re-query it by ID.
88 $post = $post instanceof \WP_Post ? $post : get_post( $post );
89
90 if ( ! $post instanceof \WP_Post ) {
91 return true;
92 }
93
94 if ( ! empty( $post->post_password ) ) {
95 return true;
96 }
97
98 if (
99 get_post_meta( $post->ID, self::META_CONTAINS_PAID_CONTENT, true )
100 || get_post_meta( $post->ID, self::META_CONTAINS_PAYWALLED_CONTENT, true )
101 || has_block( self::BLOCK_PREMIUM_CONTENT, $post )
102 || has_block( self::BLOCK_PAYWALL, $post )
103 ) {
104 return true;
105 }
106
107 return self::ACCESS_LEVEL_EVERYBODY !== self::access_level( $post->ID );
108 }
109
110 /**
111 * The part of a gated post's body that is published to everyone anyway.
112 *
113 * A `jetpack/paywall` post serves everything above the block to anonymous
114 * readers, but only because `do_blocks()` then renders that prefix: a
115 * `premium-content` block inside it hides itself, which raw markup does not.
116 *
117 * @since 0.9.2
118 *
119 * @param \WP_Post|int|null $post Post, post ID, or null for the global post.
120 * @return string Public teaser, or '' when no part of the body is public.
121 */
122 public static function public_teaser( $post = null ) {
123 $post = $post instanceof \WP_Post ? $post : get_post( $post );
124
125 if ( ! $post instanceof \WP_Post || ! empty( $post->post_password ) ) {
126 return '';
127 }
128
129 // Split on the same delimiter the subscriptions paywall splits on, so the
130 // teaser we summarize is the one the front end actually serves.
131 $delimiter = '<!-- wp:' . self::BLOCK_PAYWALL . ' /-->';
132 if ( ! str_contains( $post->post_content, $delimiter ) ) {
133 return '';
134 }
135
136 $teaser = strstr( $post->post_content, $delimiter, true );
137
138 // Callers summarize this without rendering it, so a block that withholds
139 // its own content at render time would publish here as plain text.
140 if ( str_contains( $teaser, '<!-- wp:' . self::BLOCK_NAMESPACE_PREMIUM_CONTENT . '/' ) ) {
141 return '';
142 }
143
144 return $teaser;
145 }
146
147 /**
148 * The post's Newsletter access level.
149 *
150 * Unset and non-string values read as "everybody", as the front end treats
151 * them, so a post the site renders in full keeps its summary.
152 *
153 * @param int $post_id Post ID.
154 * @return string
155 */
156 private static function access_level( $post_id ) {
157 $access_level = get_post_meta( $post_id, self::META_NEWSLETTER_ACCESS, true );
158
159 if ( empty( $access_level ) || ! is_string( $access_level ) ) {
160 return self::ACCESS_LEVEL_EVERYBODY;
161 }
162
163 return $access_level;
164 }
165 }
166