PluginProbe
Passster – Password Protect Pages and Content / 4.3.16
Passster – Password Protect Pages and Content v4.3.16
4.3.16 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 All 48 releases
← All changes | inc/class-ps-rest-api.php +212 -5 4.3.74.3.16 View file →
@@ -110,8 +110,13 @@
110 110 'required' => false,
111 111 'type' => 'integer',
112 112 'sanitize_callback' => 'absint',
113 113 ),
114 + 'post_type' => array(
115 + 'required' => false,
116 + 'type' => 'string',
117 + 'sanitize_callback' => 'sanitize_key',
118 + ),
114 119 ),
115 120 ) );
116 121 // Hash password endpoint.
117 122 register_rest_route( 'passster/v1', '/hash', array(
@@ -125,8 +130,13 @@
125 130 'sanitize_callback' => function ( $value ) {
126 131 return wp_unslash( $value );
127 132 },
128 133 ),
134 + 'post_id' => array(
135 + 'required' => true,
136 + 'type' => 'integer',
137 + 'sanitize_callback' => 'absint',
138 + ),
129 139 ),
130 140 ) );
131 141 // reCAPTCHA/hCaptcha validation endpoint.
132 142 register_rest_route( 'passster/v1', '/captcha', array(
@@ -203,8 +213,9 @@
203 213 $redirect = $request->get_param( 'redirect' );
204 214 $protection = $request->get_param( 'protection' );
205 215 $acf = $request->get_param( 'acf' );
206 216 $term_id = absint( $request->get_param( 'term_id' ) );
217 + $post_type_param = sanitize_key( (string) $request->get_param( 'post_type' ) );
207 218 // Default error response.
208 219 $error_message = $options['error'] ?? __( 'Invalid password.', 'content-protector' );
209 220 $remove_spaces = apply_filters( 'passster_remove_spaces_from_list', true );
210 221 if ( empty( $protection ) ) {
@@ -223,8 +234,9 @@
223 234 $remove_spaces
224 235 );
225 236 if ( $result['valid'] ) {
226 237 do_action( 'passster_validation_success', $input );
238 + PS_Helper::remember_unlock( hash_hmac( 'sha256', $input, get_option( 'passster_secure_key' ) ) );
227 239 $term_redirect = get_term_meta( $term_id, 'passster_redirect_url', true );
228 240 if ( !empty( $term_redirect ) ) {
229 241 return new \WP_REST_Response(array(
230 242 'success' => true,
@@ -240,8 +252,39 @@
240 252 'success' => false,
241 253 'error' => $error_message,
242 254 ), 200);
243 255 }
256 + // Post type archive protection: post_type is passed directly from the form
257 + // (no single post ID exists to key an unlock request off of on an archive page).
258 + if ( !empty( $post_type_param ) && class_exists( 'passster\\PS_Post_Type_Lock' ) && PS_Post_Type_Lock::is_post_type_protected( $post_type_param ) ) {
259 + $post_type_config = PS_Post_Type_Lock::get_post_type_config( $post_type_param );
260 + $result = $this->validate_post_type_unlock(
261 + $input,
262 + $type,
263 + $post_type_config,
264 + null,
265 + '',
266 + $remove_spaces
267 + );
268 + if ( $result['valid'] ) {
269 + do_action( 'passster_validation_success', $input );
270 + PS_Helper::remember_unlock( hash_hmac( 'sha256', $input, get_option( 'passster_secure_key' ) ) );
271 + if ( !empty( $post_type_config['passster_activate_misc_settings'] ) && !empty( $post_type_config['passster_redirect_url'] ) ) {
272 + return new \WP_REST_Response(array(
273 + 'success' => true,
274 + 'redirect' => esc_url_raw( $post_type_config['passster_redirect_url'] ),
275 + ), 200);
276 + }
277 + return new \WP_REST_Response(array(
278 + 'success' => true,
279 + 'requires_reload' => true,
280 + ), 200);
281 + }
282 + return new \WP_REST_Response(array(
283 + 'success' => false,
284 + 'error' => $error_message,
285 + ), 200);
286 + }
244 287 // Parent page protection inheritance.
245 288 $parent_id = wp_get_post_parent_id( $post_id );
246 289 if ( $parent_id ) {
247 290 $activate_protection = get_post_meta( $parent_id, 'passster_activate_protection', true );
@@ -262,10 +305,13 @@
262 305 }
263 306 // Category/taxonomy protection: if the post itself has no protection,
264 307 // check if it belongs to a protected category and validate against term meta.
265 308 $post_protection = get_post_meta( $post_id, 'passster_activate_protection', true );
266 - if ( !$post_protection && 'full' === $protection && class_exists( 'passster\\PS_Category_Lock' ) ) {
267 - $term_data = PS_Category_Lock::get_instance()->get_protected_term_for_post( $post_id );
309 + $term_data = null;
310 + if ( !$post_protection && 'full' === $protection ) {
311 + if ( class_exists( 'passster\\PS_Category_Lock' ) ) {
312 + $term_data = PS_Category_Lock::get_instance()->get_protected_term_for_post( $post_id );
313 + }
268 314 if ( $term_data ) {
269 315 // Use term redirect if no redirect was sent from the frontend.
270 316 if ( empty( $redirect ) ) {
271 317 $redirect = get_term_meta( $term_data['term_id'], 'passster_redirect_url', true );
@@ -299,8 +345,9 @@
299 345 $input,
300 346 'full'
301 347 );
302 348 do_action( 'passster_validation_success', $input );
349 + PS_Helper::remember_unlock( hash_hmac( 'sha256', $input, get_option( 'passster_secure_key' ) ) );
303 350 return new \WP_REST_Response($response_data, 200);
304 351 }
305 352 // Category protection exists but validation failed.
306 353 return new \WP_REST_Response(array(
@@ -307,8 +354,53 @@
307 354 'success' => false,
308 355 'error' => $error_message,
309 356 ), 200);
310 357 }
358 + // Post type level protection: applies only when the post has neither its
359 + // own protection nor a protected category/term (both take precedence).
360 + if ( class_exists( 'passster\\PS_Post_Type_Lock' ) ) {
361 + $post_type = get_post_type( $post_id );
362 + if ( $post_type && PS_Post_Type_Lock::is_post_type_protected( $post_type ) ) {
363 + $config = PS_Post_Type_Lock::get_post_type_config( $post_type );
364 + if ( empty( $redirect ) && !empty( $config['passster_activate_misc_settings'] ) && !empty( $config['passster_redirect_url'] ) ) {
365 + $redirect = $config['passster_redirect_url'];
366 + }
367 + $result = $this->validate_post_type_unlock(
368 + $input,
369 + $type,
370 + $config,
371 + $post,
372 + $content,
373 + $remove_spaces
374 + );
375 + if ( $result['valid'] ) {
376 + $response_data = array(
377 + 'success' => true,
378 + );
379 + if ( !empty( $redirect ) ) {
380 + $response_data['redirect'] = $redirect;
381 + } elseif ( $this->content_uses_page_builder( $result['content'], $post_id ) ) {
382 + $response_data['requires_reload'] = true;
383 + } else {
384 + $response_data['content'] = apply_filters( 'the_content', str_replace( '{post-id}', $post_id, $result['content'] ) );
385 + }
386 + do_action(
387 + 'passsster_track_record',
388 + $post_id,
389 + $input,
390 + 'full'
391 + );
392 + do_action( 'passster_validation_success', $input );
393 + PS_Helper::remember_unlock( hash_hmac( 'sha256', $input, get_option( 'passster_secure_key' ) ) );
394 + return new \WP_REST_Response($response_data, 200);
395 + }
396 + // Post type protection exists but validation failed.
397 + return new \WP_REST_Response(array(
398 + 'success' => false,
399 + 'error' => $error_message,
400 + ), 200);
401 + }
402 + }
311 403 }
312 404 // Validate based on type.
313 405 $valid = false;
314 406 $result_content = '';
@@ -458,8 +550,9 @@
458 550 $input,
459 551 $source
460 552 );
461 553 do_action( 'passster_validation_success', $input );
554 + PS_Helper::remember_unlock( hash_hmac( 'sha256', $input, get_option( 'passster_secure_key' ) ) );
462 555 return new \WP_REST_Response($response_data, 200);
463 556 }
464 557
465 558 /**
@@ -933,8 +1026,109 @@
933 1026 'content' => '',
934 1027 );
935 1028 }
936 1029
1030 + /**
1031 + * Validate unlock for posts protected wholesale via post type level protection
1032 + * (password stored in the post type's configuration, not on the post itself).
1033 + *
1034 + * @param string $input User input.
1035 + * @param string $type Protection type (password, passwords, password_list, password_lists).
1036 + * @param array $config Post type configuration.
1037 + * @param \WP_Post $post Post object.
1038 + * @param string $content Post content.
1039 + * @param bool $remove_spaces Whether to remove spaces from password lists.
1040 + * @return array
1041 + */
1042 + private function validate_post_type_unlock(
1043 + $input,
1044 + $type,
1045 + array $config,
1046 + $post,
1047 + $content,
1048 + $remove_spaces
1049 + ) {
1050 + // $post is null for archive-level unlocks, where there's no single post to check.
1051 + if ( $post && 'publish' !== $post->post_status ) {
1052 + return array(
1053 + 'valid' => false,
1054 + 'content' => '',
1055 + );
1056 + }
1057 + switch ( $type ) {
1058 + case 'password':
1059 + if ( !empty( $config['passster_password'] ) && $input === $config['passster_password'] ) {
1060 + return array(
1061 + 'valid' => true,
1062 + 'content' => $content,
1063 + );
1064 + }
1065 + break;
1066 + case 'passwords':
1067 + $passwords_str = $config['passster_passwords'];
1068 + if ( $remove_spaces ) {
1069 + $passwords_str = str_replace( ' ', '', $passwords_str );
1070 + }
1071 + $passwords = explode( ',', $passwords_str );
1072 + if ( !empty( $passwords ) && in_array( $input, $passwords, true ) ) {
1073 + return array(
1074 + 'valid' => true,
1075 + 'content' => $content,
1076 + );
1077 + }
1078 + break;
1079 + case 'password_list':
1080 + $list_id = $config['passster_password_list'];
1081 + if ( !empty( $list_id ) ) {
1082 + $passwords_str = get_post_meta( $list_id, 'passster_passwords', true );
1083 + if ( $remove_spaces ) {
1084 + $passwords_str = str_replace( ' ', '', $passwords_str );
1085 + }
1086 + $passwords = explode( ',', $passwords_str );
1087 + if ( in_array( $input, $passwords, true ) ) {
1088 + do_action(
1089 + 'passster_validation_success_list',
1090 + $input,
1091 + $list_id,
1092 + ( $post ? $post->ID : 0 )
1093 + );
1094 + PS_Conditional::maybe_expire_password_from_list__premium_only( $input, $passwords, $list_id );
1095 + return array(
1096 + 'valid' => true,
1097 + 'content' => $content,
1098 + );
1099 + }
1100 + }
1101 + break;
1102 + case 'password_lists':
1103 + foreach ( (array) $config['passster_password_lists'] as $list_id ) {
1104 + $passwords_str = get_post_meta( $list_id, 'passster_passwords', true );
1105 + if ( $remove_spaces ) {
1106 + $passwords_str = str_replace( ' ', '', $passwords_str );
1107 + }
1108 + $passwords = explode( ',', $passwords_str );
1109 + if ( in_array( $input, $passwords, true ) ) {
1110 + do_action(
1111 + 'passster_validation_success_list',
1112 + $input,
1113 + $list_id,
1114 + ( $post ? $post->ID : 0 )
1115 + );
1116 + PS_Conditional::maybe_expire_password_from_list__premium_only( $input, $passwords, $list_id );
1117 + return array(
1118 + 'valid' => true,
1119 + 'content' => $content,
1120 + );
1121 + }
1122 + }
1123 + break;
1124 + }
1125 + return array(
1126 + 'valid' => false,
1127 + 'content' => '',
1128 + );
1129 + }
1130 +
937 1131 private function get_block_password( $post_id, $block_id ) {
938 1132 $post = get_post( $post_id );
939 1133 if ( !$post ) {
940 1134 return array(
@@ -1065,9 +1259,18 @@
1065 1259 * @return \WP_REST_Response
1066 1260 */
1067 1261 public function hash_password( \WP_REST_Request $request ) {
1068 1262 $password = $request->get_param( 'password' );
1263 + $post_id = $request->get_param( 'post_id' );
1264 + $real_password = get_post_meta( $post_id, 'passster_password', true );
1265 + if ( empty( $real_password ) || !hash_equals( $real_password, $password ) ) {
1266 + return new \WP_REST_Response(array(
1267 + 'success' => false,
1268 + 'error' => __( 'Invalid password.', 'content-protector' ),
1269 + ), 200);
1270 + }
1069 1271 $hashed = hash_hmac( 'sha256', $password, get_option( 'passster_secure_key' ) );
1272 + PS_Helper::remember_unlock( $hashed );
1070 1273 return new \WP_REST_Response(array(
1071 1274 'success' => true,
1072 1275 'hash' => $hashed,
1073 1276 ), 200);
@@ -1118,13 +1321,15 @@
1118 1321 }
1119 1322 // Get content based on protection type (mirrors AJAX logic).
1120 1323 $content = '';
1121 1324 $requires_reload = false;
1325 + $captcha_protection_types = array('recaptcha', 'turnstile');
1122 1326 if ( 'full' !== $protection ) {
1123 1327 if ( 'area' === $protection ) {
1124 1328 if ( !empty( $area_id ) ) {
1125 1329 $area = get_post( $area_id );
1126 - if ( $area && 'protected_areas' === $area->post_type && 'publish' === $area->post_status ) {
1330 + $area_protection_type = get_post_meta( $area_id, 'passster_protection_type', true );
1331 + if ( $area && 'protected_areas' === $area->post_type && 'publish' === $area->post_status && in_array( $area_protection_type, $captcha_protection_types, true ) ) {
1127 1332 $content = apply_filters( 'passster_compatibility_actions', $area->post_content, $area_id );
1128 1333 }
1129 1334 }
1130 1335 } else {
@@ -1131,15 +1336,16 @@
1131 1336 // Shortcode protection - extract content using captcha_id.
1132 1337 $post = get_post( $post_id );
1133 1338 if ( $post ) {
1134 1339 $post_content = apply_filters( 'passster_compatibility_actions', $post->post_content, $post_id );
1135 - $content = apply_filters( 'passster_compatibility_actions', PS_Helper::get_shortcode_content( $post_content, $captcha_id ) );
1340 + $content = apply_filters( 'passster_compatibility_actions', PS_Helper::get_shortcode_content( $post_content, $captcha_id, 'captcha' ) );
1136 1341 }
1137 1342 }
1138 1343 } else {
1139 1344 $post = get_post( $post_id );
1140 1345 $post_protection = get_post_meta( $post_id, 'passster_activate_protection', true );
1141 - if ( $post && 'publish' === $post->post_status && $post_protection ) {
1346 + $post_protection_type = get_post_meta( $post_id, 'passster_protection_type', true );
1347 + if ( $post && 'publish' === $post->post_status && $post_protection && in_array( $post_protection_type, $captcha_protection_types, true ) ) {
1142 1348 $content = apply_filters( 'passster_compatibility_actions', $post->post_content, $post_id );
1143 1349 } elseif ( $post && 'publish' === $post->post_status && !$post_protection && class_exists( 'passster\\PS_Category_Lock' ) && PS_Category_Lock::get_instance()->get_protected_term_for_post( $post_id ) ) {
1144 1350 $requires_reload = true;
1145 1351 }
@@ -1160,8 +1366,9 @@
1160 1366 // Success response.
1161 1367 $response_data = array(
1162 1368 'success' => true,
1163 1369 );
1370 + PS_Helper::remember_unlock( hash_hmac( 'sha256', 'captcha-verified', get_option( 'passster_secure_key' ) ) );
1164 1371 if ( !empty( $redirect ) ) {
1165 1372 $response_data['redirect'] = $redirect;
1166 1373 } elseif ( $requires_reload ) {
1167 1374 $response_data['requires_reload'] = true;