PluginProbe
Passster – Password Protect Pages and Content / 4.3.5
Passster – Password Protect Pages and Content v4.3.5
4.3.15 4.3.14 4.3.12 4.3.13 4.3.11 4.3.10 4.3.9 4.3.8 4.3.7 4.3.6 4.3.5 trunk 3.5.4 3.5.5.2 3.5.5.8 3.5.5.9 4.0 4.1.4 4.2.10 4.2.11 4.2.12 4.2.13 4.2.14 4.2.15 4.2.16 All 47 releases
content-protector / inc / class-ps-public.php

class-ps-public.php in Passster – Password Protect Pages and Content 4.3.5, at inc/class-ps-public.php

424 lines 17.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace passster;
4
5 use Exception;
6 class PS_Public {
7 /**
8 * Contains instance or null
9 *
10 * @var object|null
11 */
12 private static $instance = null;
13
14 /**
15 * Track which posts have already had their protection form rendered
16 * to prevent duplicate forms on page builders like Avada.
17 *
18 * @var array
19 */
20 private static $rendered_protection = array();
21
22 /**
23 * Constructor for PS_Public
24 */
25 public function __construct() {
26 add_shortcode( 'content_protector', array($this, 'render_shortcode') );
27 add_shortcode( 'passster', array($this, 'render_shortcode') );
28 add_filter( 'the_content', array($this, 'filter_the_content') );
29 add_filter( 'acf_the_content', array($this, 'filter_the_content') );
30 add_filter( 'get_the_excerpt', array($this, 'filter_the_content') );
31 add_action( 'template_redirect', array($this, 'check_global_proctection') );
32 add_action( 'wp_enqueue_scripts', array($this, 'add_public_scripts') );
33 }
34
35 /**
36 * Returns instance of PS_Public.
37 *
38 * @return object
39 */
40 public static function get_instance() {
41 if ( null === self::$instance ) {
42 self::$instance = new self();
43 }
44 return self::$instance;
45 }
46
47 /**
48 * Render the Passster shortcode.
49 *
50 * @param array $atts array of attributes.
51 * @param string|null $content the current content.
52 *
53 * @return string
54 */
55 public function render_shortcode( array $atts, string $content = null ) : string {
56 // Schedule check for protected areas (PRO only).
57 if ( !empty( $atts['area'] ) && \passster_fs()->is_plan_or_trial__premium_only( 'pro' ) ) {
58 $area_id = absint( $atts['area'] );
59 $schedule_enabled = get_post_meta( $area_id, 'passster_schedule_enabled', true );
60 if ( $schedule_enabled ) {
61 $schedule_start = get_post_meta( $area_id, 'passster_schedule_start', true );
62 $schedule_end = get_post_meta( $area_id, 'passster_schedule_end', true );
63 $now = current_time( 'timestamp' );
64 $in_schedule = true;
65 if ( !empty( $schedule_start ) && $now < strtotime( $schedule_start ) ) {
66 $in_schedule = false;
67 }
68 if ( !empty( $schedule_end ) && $now > strtotime( $schedule_end ) ) {
69 $in_schedule = false;
70 }
71 if ( !$in_schedule ) {
72 $area = get_post( $area_id );
73 if ( $area && ('publish' === $area->post_status || current_user_can( 'edit_post', $area_id )) ) {
74 return apply_filters( 'the_content', str_replace( '{post-id}', get_the_id(), $area->post_content ) );
75 }
76 return $content ?? '';
77 }
78 }
79 }
80 // check if valid before restrict anything.
81 $valid = PS_Conditional::is_valid( $atts );
82 $options = get_option( 'passster' );
83 if ( $valid ) {
84 if ( !empty( $atts['area'] ) ) {
85 $area_id = esc_html( $atts['area'] );
86 $area = get_post( $area_id );
87 if ( 'publish' === $area->post_status || current_user_can( 'edit_post', $area_id ) ) {
88 $content = $area->post_content;
89 do_action( 'passster_content_unlocked' );
90 return apply_filters( 'the_content', str_replace( '{post-id}', get_the_id(), $content ) );
91 }
92 } else {
93 $content = apply_filters( 'the_content', $content );
94 do_action( 'passster_content_unlocked' );
95 return apply_filters( 'passster_content', $content );
96 }
97 }
98 // do nothing if no atts.
99 if ( empty( $atts ) ) {
100 return $content;
101 }
102 // Set default form.
103 $form = PS_Form::get_password_form();
104 // Password.
105 if ( !empty( $atts['password'] ) ) {
106 $form = PS_Form::get_password_form();
107 $form = str_replace( '[PASSSTER_TYPE]', 'password', $form );
108 }
109 // Area.
110 if ( !empty( $atts['area'] ) ) {
111 $area_id = absint( $atts['area'] );
112 $form = str_replace( '[PASSSTER_AREA]', $area_id, $form );
113 }
114 // Page.
115 if ( !empty( $atts['protection'] ) ) {
116 $form = str_replace( '[PASSSTER_PROTECTION]', 'full', $form );
117 } elseif ( !empty( $atts['area'] ) ) {
118 $form = str_replace( '[PASSSTER_PROTECTION]', 'area', $form );
119 }
120 // Redirect.
121 if ( !empty( $atts['redirect'] ) ) {
122 $form = str_replace( '[PASSSTER_REDIRECT]', esc_url( $atts['redirect'] ), $form );
123 } else {
124 $form = str_replace( '[PASSSTER_REDIRECT]', '', $form );
125 }
126 // headline tag.
127 $allowed_headline_tags = array(
128 'span',
129 'p',
130 'div',
131 'h1',
132 'h2',
133 'h3',
134 'h4',
135 'h5',
136 'h6'
137 );
138 $headline_tag = ( isset( $options['headline_tag'] ) && in_array( $options['headline_tag'], $allowed_headline_tags, true ) ? $options['headline_tag'] : 'span' );
139 $form = str_replace( '[PASSSTER_HEADLINE_TAG]', $headline_tag, $form );
140 // headline.
141 if ( !empty( $options['hide_headline'] ) ) {
142 $form = str_replace( '[PASSSTER_FORM_HEADLINE]', '', $form );
143 } elseif ( !empty( $atts['headline'] ) ) {
144 $form = str_replace( '[PASSSTER_FORM_HEADLINE]', esc_html( $atts['headline'] ), $form );
145 } else {
146 $form = str_replace( '[PASSSTER_FORM_HEADLINE]', esc_html( $options['headline'] ), $form );
147 }
148 // instruction.
149 if ( !empty( $atts['instruction'] ) ) {
150 $decoded_instruction = base64_decode( $atts['instruction'] );
151 $decoded_instruction = html_entity_decode( $decoded_instruction );
152 $sanitized_instruction = wp_kses_post( $decoded_instruction );
153 $form = str_replace( '[PASSSTER_FORM_INSTRUCTIONS]', $sanitized_instruction, $form );
154 } else {
155 $form = str_replace( '[PASSSTER_FORM_INSTRUCTIONS]', wp_kses_post( $options['instruction'] ), $form );
156 }
157 // placeholder.
158 if ( !empty( $atts['placeholder'] ) ) {
159 $form = str_replace( '[PASSSTER_PLACEHOLDER]', esc_attr( $atts['placeholder'] ), $form );
160 } else {
161 $form = str_replace( '[PASSSTER_PLACEHOLDER]', esc_attr( $options['placeholder'] ), $form );
162 }
163 // label.
164 $form = str_replace( '[PASSSTER_LABEL]', esc_html__( 'Enter your password', 'content-protector' ), $form );
165 // button.
166 if ( !empty( $atts['button'] ) ) {
167 $form = str_replace( '[PASSSTER_BUTTON_LABEL]', esc_html( $atts['button'] ), $form );
168 } else {
169 $form = str_replace( '[PASSSTER_BUTTON_LABEL]', esc_html( $options['button_label'] ), $form );
170 }
171 // modify id.
172 if ( !empty( $atts['id'] ) ) {
173 $form = str_replace( '[PASSSTER_ID]', 'ps-' . esc_attr( $atts['id'] ), $form );
174 } else {
175 $form = str_replace( '[PASSSTER_ID]', 'ps-' . wp_rand( 10, 1000 ), $form );
176 }
177 // post id (per-form, for correct REST unlock on archive pages with multiple protected posts).
178 $form = str_replace( '[PASSSTER_POST_ID]', absint( get_the_ID() ), $form );
179 // term id (for category archive protection — passed to REST API so it can validate against term meta).
180 $term_id_val = ( !empty( $atts['term_id'] ) ? absint( $atts['term_id'] ) : 0 );
181 $form = str_replace( '[PASSSTER_TERM_ID]', $term_id_val, $form );
182 // hide or not.
183 if ( !empty( $atts['hide'] ) ) {
184 $form = str_replace( '[PASSSTER_HIDE]', ' passster-hide', $form );
185 } else {
186 $form = str_replace( '[PASSSTER_HIDE]', '', $form );
187 }
188 // ACF field.
189 if ( !empty( $atts['acf'] ) ) {
190 $form = str_replace( '[PASSSTER_ACF]', ' data-acf="' . esc_url( $atts['acf'] ) . '"', $form );
191 } else {
192 $form = str_replace( '[PASSSTER_ACF]', '', $form );
193 }
194 return $form;
195 }
196
197 /**
198 * Filters the_content with Passster.
199 *
200 * @param string $content given content.
201 *
202 * @return string
203 * @throws Exception
204 */
205 public function filter_the_content( string $content ) : string {
206 $post_id = get_the_id();
207 // Prevent duplicate form rendering (fixes issue with Avada and other page builders)
208 if ( isset( self::$rendered_protection[$post_id] ) ) {
209 // Already rendered the protection form for this post, return protected content placeholder
210 // or the form that was already generated
211 return self::$rendered_protection[$post_id]['form'] ?? $content;
212 }
213 $parent_id = wp_get_post_parent_id( $post_id );
214 if ( $parent_id ) {
215 $activate_protection = get_post_meta( $parent_id, 'passster_activate_protection', true );
216 $children_protection = get_post_meta( $parent_id, 'passster_protect_child_pages', true );
217 if ( $activate_protection && $children_protection ) {
218 $post_id = $parent_id;
219 // Check parent too
220 if ( isset( self::$rendered_protection[$post_id] ) ) {
221 return self::$rendered_protection[$post_id]['form'] ?? $content;
222 }
223 }
224 }
225 $activate_protection = get_post_meta( $post_id, 'passster_activate_protection', true );
226 // user restriction.
227 $user_restriction_type = get_post_meta( $post_id, 'passster_user_restriction_type', true );
228 $user_restriction = get_post_meta( $post_id, 'passster_user_restriction', true );
229 // Redirection.
230 $redirection = get_post_meta( $post_id, 'passster_redirect_url', true );
231 // texts.
232 $headline = get_post_meta( $post_id, 'passster_headline', true );
233 $instruction = get_post_meta( $post_id, 'passster_instruction', true );
234 $placeholder = get_post_meta( $post_id, 'passster_placeholder', true );
235 $button = get_post_meta( $post_id, 'passster_button', true );
236 $id = get_post_meta( $post_id, 'passster_id', true );
237 if ( !$activate_protection ) {
238 return $content;
239 }
240 // build atts array to validate.
241 $atts = array();
242 $shortcode = '';
243 $password = get_post_meta( $post_id, 'passster_password', true );
244 $atts['password'] = $password;
245 $shortcode = '[passster password="' . $password . '" protection="full" ';
246 if ( !empty( $redirection ) ) {
247 $shortcode .= 'redirect="' . $redirection . '" ';
248 }
249 if ( !empty( $headline ) ) {
250 $shortcode .= 'headline="' . $headline . '" ';
251 }
252 if ( !empty( $instruction ) ) {
253 $shortcode .= 'instruction="' . base64_encode( $instruction ) . '" ';
254 }
255 if ( !empty( $placeholder ) ) {
256 $shortcode .= 'placeholder="' . $placeholder . '" ';
257 }
258 if ( !empty( $button ) ) {
259 $shortcode .= 'button="' . $button . '" ';
260 }
261 if ( !empty( $id ) ) {
262 $shortcode .= 'id="' . $id . '" ';
263 }
264 $shortcode .= ']{content}[/passster]';
265 // check if valid before restrict anything.
266 $valid = PS_Conditional::is_valid( $atts );
267 if ( $valid ) {
268 return $content;
269 }
270 // replace placeholder with content.
271 $shortcode = str_replace( '{content}', $content, $shortcode );
272 // Generate the form
273 $rendered_form = do_shortcode( $shortcode );
274 // Store reference to prevent duplicate rendering (Avada, Elementor, etc.)
275 self::$rendered_protection[$post_id] = array(
276 'form' => $rendered_form,
277 );
278 return $rendered_form;
279 }
280
281 /**
282 * Redirect if global protection is activated and no password is set.
283 *
284 * @return void
285 * @throws Exception
286 */
287 public function check_global_proctection() {
288 $options = get_option( 'passster' );
289 $post_id = get_queried_object_id();
290 if ( !$post_id ) {
291 return;
292 }
293 // Allow Elementor editing the page.
294 $elementor_preview = filter_input( INPUT_GET, 'elementor-preview', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
295 // Allow Live Canvas Editor.
296 $live_canvas_preview = filter_input( INPUT_GET, 'lc_action_launch_editing', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
297 if ( is_preview() || $elementor_preview || $live_canvas_preview ) {
298 if ( is_user_logged_in() && current_user_can( 'edit_post', $post_id ) ) {
299 return;
300 }
301 }
302 if ( !isset( $options['global_protection_id'] ) ) {
303 return;
304 }
305 if ( !isset( $options['activate_global_protection'] ) ) {
306 return;
307 }
308 // Build $atts array based on protection settings.
309 $post_id = esc_html( $options['global_protection_id'] );
310 $is_active = esc_html( $options['activate_global_protection'] );
311 $atts = array();
312 $password = get_post_meta( $post_id, 'passster_password', true );
313 $atts['password'] = esc_html( $password );
314 if ( !empty( $post_id ) ) {
315 if ( $is_active ) {
316 if ( is_page( $post_id ) || is_single( $post_id ) ) {
317 return;
318 }
319 // Check excluded pages.
320 if ( isset( $options['exclude_pages'] ) ) {
321 foreach ( $options['exclude_pages'] as $excluded_page_id ) {
322 if ( is_page( $excluded_page_id ) ) {
323 return;
324 }
325 }
326 }
327 // Check if cookie is set.
328 $cookie = esc_html( $_COOKIE['passster'] );
329 if ( !PS_Conditional::is_valid( $atts ) ) {
330 $global_protection_url = get_permalink( $post_id );
331 $pass_param = filter_input( INPUT_GET, 'pass', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
332 if ( !empty( $pass_param ) ) {
333 $global_protection_url = add_query_arg( 'pass', $pass_param, $global_protection_url );
334 }
335 wp_redirect( esc_url_raw( $global_protection_url ) );
336 exit;
337 }
338 }
339 }
340 }
341
342 /**
343 * Enqueue scripts for shortcode
344 *
345 * @return void
346 */
347 public function add_public_scripts() {
348 $suffix = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min' );
349 $options = get_option( 'passster' );
350 // Only load CSS if not disabled (allows themes to style the form)
351 if ( empty( $options['disable_css'] ) ) {
352 wp_enqueue_style(
353 'passster-public',
354 PASSSTER_URL . '/assets/public/passster-public' . $suffix . '.css',
355 array(),
356 PASSSTER_VERSION,
357 'all'
358 );
359 }
360 wp_enqueue_script(
361 'passster-cookie',
362 PASSSTER_URL . '/assets/public/cookie.js',
363 array('jquery', 'wp-api-fetch'),
364 PASSSTER_VERSION,
365 false
366 );
367 wp_enqueue_script(
368 'passster-public',
369 PASSSTER_URL . '/assets/public/passster-public' . $suffix . '.js',
370 array('jquery', 'passster-cookie'),
371 PASSSTER_VERSION,
372 false
373 );
374 $shortcodes = array();
375 if ( isset( $options['third_party_shortcodes'] ) && !empty( $options['third_party_shortcodes'] ) ) {
376 $shortcodes_in_options = explode( ',', $options['third_party_shortcodes'] );
377 if ( is_array( $shortcodes_in_options ) ) {
378 foreach ( $shortcodes_in_options as $shortcode ) {
379 $shortcodes[$shortcode] = do_shortcode( str_replace( '{post-id}', get_the_id(), $shortcode ) );
380 }
381 }
382 }
383 $args = array(
384 'ajax_url' => admin_url() . 'admin-ajax.php',
385 'rest_url' => get_rest_url(),
386 'nonce' => wp_create_nonce( 'ps-password-nonce' ),
387 'hash_nonce' => wp_create_nonce( 'ps-hash-nonce' ),
388 'logout_nonce' => wp_create_nonce( 'ps-logout-nonce' ),
389 'post_id' => get_the_id(),
390 'shortcodes' => $shortcodes,
391 'permalink' => get_permalink( get_the_id() ),
392 );
393 if ( isset( $options['cookie_duration_unit'] ) ) {
394 $args['cookie_duration_unit'] = esc_html( $options['cookie_duration_unit'] );
395 } else {
396 $args['cookie_duration_unit'] = 'days';
397 }
398 if ( isset( $options['cookie_duration'] ) ) {
399 $args['cookie_duration'] = esc_html( $options['cookie_duration'] );
400 } else {
401 $args['cookie_duration'] = 1;
402 }
403 if ( isset( $options['disable_cookie'] ) ) {
404 $args['disable_cookie'] = esc_html( $options['disable_cookie'] );
405 } else {
406 $args['disable_cookie'] = false;
407 }
408 $args['unlock_mode'] = !empty( $options['unlock_mode'] );
409 wp_localize_script( 'passster-public', 'ps_ajax', $args );
410 // if password type hint used.
411 $password_typing = $options['show_password'];
412 if ( $password_typing ) {
413 wp_enqueue_script(
414 'password-typing',
415 PASSSTER_URL . '/assets/public/password-typing.js',
416 array('jquery'),
417 PASSSTER_VERSION,
418 false
419 );
420 }
421 }
422
423 }
424