PluginProbe
Passster – Password Protect Pages and Content / 4.3.6
Passster – Password Protect Pages and Content v4.3.6
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.6, at inc/class-ps-public.php

425 lines 18.2 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 $overwrite_defaults = get_post_meta( $post_id, 'passster_activate_overwrite_defaults', true );
233 $headline = ( $overwrite_defaults ? get_post_meta( $post_id, 'passster_headline', true ) : '' );
234 $instruction = ( $overwrite_defaults ? get_post_meta( $post_id, 'passster_instruction', true ) : '' );
235 $placeholder = ( $overwrite_defaults ? get_post_meta( $post_id, 'passster_placeholder', true ) : '' );
236 $button = ( $overwrite_defaults ? get_post_meta( $post_id, 'passster_button', true ) : '' );
237 $id = get_post_meta( $post_id, 'passster_id', true );
238 if ( !$activate_protection ) {
239 return $content;
240 }
241 // build atts array to validate.
242 $atts = array();
243 $shortcode = '';
244 $password = get_post_meta( $post_id, 'passster_password', true );
245 $atts['password'] = $password;
246 $shortcode = '[passster password="' . $password . '" protection="full" ';
247 if ( !empty( $redirection ) ) {
248 $shortcode .= 'redirect="' . $redirection . '" ';
249 }
250 if ( !empty( $headline ) ) {
251 $shortcode .= 'headline="' . $headline . '" ';
252 }
253 if ( !empty( $instruction ) ) {
254 $shortcode .= 'instruction="' . base64_encode( $instruction ) . '" ';
255 }
256 if ( !empty( $placeholder ) ) {
257 $shortcode .= 'placeholder="' . $placeholder . '" ';
258 }
259 if ( !empty( $button ) ) {
260 $shortcode .= 'button="' . $button . '" ';
261 }
262 if ( !empty( $id ) ) {
263 $shortcode .= 'id="' . $id . '" ';
264 }
265 $shortcode .= ']{content}[/passster]';
266 // check if valid before restrict anything.
267 $valid = PS_Conditional::is_valid( $atts );
268 if ( $valid ) {
269 return $content;
270 }
271 // replace placeholder with content.
272 $shortcode = str_replace( '{content}', $content, $shortcode );
273 // Generate the form
274 $rendered_form = do_shortcode( $shortcode );
275 // Store reference to prevent duplicate rendering (Avada, Elementor, etc.)
276 self::$rendered_protection[$post_id] = array(
277 'form' => $rendered_form,
278 );
279 return $rendered_form;
280 }
281
282 /**
283 * Redirect if global protection is activated and no password is set.
284 *
285 * @return void
286 * @throws Exception
287 */
288 public function check_global_proctection() {
289 $options = get_option( 'passster' );
290 $post_id = get_queried_object_id();
291 if ( !$post_id ) {
292 return;
293 }
294 // Allow Elementor editing the page.
295 $elementor_preview = filter_input( INPUT_GET, 'elementor-preview', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
296 // Allow Live Canvas Editor.
297 $live_canvas_preview = filter_input( INPUT_GET, 'lc_action_launch_editing', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
298 if ( is_preview() || $elementor_preview || $live_canvas_preview ) {
299 if ( is_user_logged_in() && current_user_can( 'edit_post', $post_id ) ) {
300 return;
301 }
302 }
303 if ( !isset( $options['global_protection_id'] ) ) {
304 return;
305 }
306 if ( !isset( $options['activate_global_protection'] ) ) {
307 return;
308 }
309 // Build $atts array based on protection settings.
310 $post_id = esc_html( $options['global_protection_id'] );
311 $is_active = esc_html( $options['activate_global_protection'] );
312 $atts = array();
313 $password = get_post_meta( $post_id, 'passster_password', true );
314 $atts['password'] = esc_html( $password );
315 if ( !empty( $post_id ) ) {
316 if ( $is_active ) {
317 if ( is_page( $post_id ) || is_single( $post_id ) ) {
318 return;
319 }
320 // Check excluded pages.
321 if ( isset( $options['exclude_pages'] ) ) {
322 foreach ( $options['exclude_pages'] as $excluded_page_id ) {
323 if ( is_page( $excluded_page_id ) ) {
324 return;
325 }
326 }
327 }
328 // Check if cookie is set.
329 $cookie = esc_html( $_COOKIE['passster'] );
330 if ( !PS_Conditional::is_valid( $atts ) ) {
331 $global_protection_url = get_permalink( $post_id );
332 $pass_param = filter_input( INPUT_GET, 'pass', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
333 if ( !empty( $pass_param ) ) {
334 $global_protection_url = add_query_arg( 'pass', $pass_param, $global_protection_url );
335 }
336 wp_redirect( esc_url_raw( $global_protection_url ) );
337 exit;
338 }
339 }
340 }
341 }
342
343 /**
344 * Enqueue scripts for shortcode
345 *
346 * @return void
347 */
348 public function add_public_scripts() {
349 $suffix = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min' );
350 $options = get_option( 'passster' );
351 // Only load CSS if not disabled (allows themes to style the form)
352 if ( empty( $options['disable_css'] ) ) {
353 wp_enqueue_style(
354 'passster-public',
355 PASSSTER_URL . '/assets/public/passster-public' . $suffix . '.css',
356 array(),
357 PASSSTER_VERSION,
358 'all'
359 );
360 }
361 wp_enqueue_script(
362 'passster-cookie',
363 PASSSTER_URL . '/assets/public/cookie.js',
364 array('jquery', 'wp-api-fetch'),
365 PASSSTER_VERSION,
366 false
367 );
368 wp_enqueue_script(
369 'passster-public',
370 PASSSTER_URL . '/assets/public/passster-public' . $suffix . '.js',
371 array('jquery', 'passster-cookie'),
372 PASSSTER_VERSION,
373 false
374 );
375 $shortcodes = array();
376 if ( isset( $options['third_party_shortcodes'] ) && !empty( $options['third_party_shortcodes'] ) ) {
377 $shortcodes_in_options = explode( ',', $options['third_party_shortcodes'] );
378 if ( is_array( $shortcodes_in_options ) ) {
379 foreach ( $shortcodes_in_options as $shortcode ) {
380 $shortcodes[$shortcode] = do_shortcode( str_replace( '{post-id}', get_the_id(), $shortcode ) );
381 }
382 }
383 }
384 $args = array(
385 'ajax_url' => admin_url() . 'admin-ajax.php',
386 'rest_url' => get_rest_url(),
387 'nonce' => wp_create_nonce( 'ps-password-nonce' ),
388 'hash_nonce' => wp_create_nonce( 'ps-hash-nonce' ),
389 'logout_nonce' => wp_create_nonce( 'ps-logout-nonce' ),
390 'post_id' => get_the_id(),
391 'shortcodes' => $shortcodes,
392 'permalink' => get_permalink( get_the_id() ),
393 );
394 if ( isset( $options['cookie_duration_unit'] ) ) {
395 $args['cookie_duration_unit'] = esc_html( $options['cookie_duration_unit'] );
396 } else {
397 $args['cookie_duration_unit'] = 'days';
398 }
399 if ( isset( $options['cookie_duration'] ) ) {
400 $args['cookie_duration'] = esc_html( $options['cookie_duration'] );
401 } else {
402 $args['cookie_duration'] = 1;
403 }
404 if ( isset( $options['disable_cookie'] ) ) {
405 $args['disable_cookie'] = esc_html( $options['disable_cookie'] );
406 } else {
407 $args['disable_cookie'] = false;
408 }
409 $args['unlock_mode'] = !empty( $options['unlock_mode'] );
410 wp_localize_script( 'passster-public', 'ps_ajax', $args );
411 // if password type hint used.
412 $password_typing = $options['show_password'];
413 if ( $password_typing ) {
414 wp_enqueue_script(
415 'password-typing',
416 PASSSTER_URL . '/assets/public/password-typing.js',
417 array('jquery'),
418 PASSSTER_VERSION,
419 false
420 );
421 }
422 }
423
424 }
425