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 +218 -6 4.3.54.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);
@@ -1117,13 +1320,16 @@
1117 1320 ), 200);
1118 1321 }
1119 1322 // Get content based on protection type (mirrors AJAX logic).
1120 1323 $content = '';
1324 + $requires_reload = false;
1325 + $captcha_protection_types = array('recaptcha', 'turnstile');
1121 1326 if ( 'full' !== $protection ) {
1122 1327 if ( 'area' === $protection ) {
1123 1328 if ( !empty( $area_id ) ) {
1124 1329 $area = get_post( $area_id );
1125 - 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 ) ) {
1126 1332 $content = apply_filters( 'passster_compatibility_actions', $area->post_content, $area_id );
1127 1333 }
1128 1334 }
1129 1335 } else {
@@ -1130,16 +1336,19 @@
1130 1336 // Shortcode protection - extract content using captcha_id.
1131 1337 $post = get_post( $post_id );
1132 1338 if ( $post ) {
1133 1339 $post_content = apply_filters( 'passster_compatibility_actions', $post->post_content, $post_id );
1134 - $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' ) );
1135 1341 }
1136 1342 }
1137 1343 } else {
1138 - // Full page protection - return full post content.
1139 1344 $post = get_post( $post_id );
1140 - if ( $post ) {
1345 + $post_protection = get_post_meta( $post_id, 'passster_activate_protection', true );
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 ) ) {
1141 1348 $content = apply_filters( 'passster_compatibility_actions', $post->post_content, $post_id );
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 ) ) {
1350 + $requires_reload = true;
1142 1351 }
1143 1352 }
1144 1353 // Track record.
1145 1354 $source = 'shortcode';
@@ -1157,10 +1366,13 @@
1157 1366 // Success response.
1158 1367 $response_data = array(
1159 1368 'success' => true,
1160 1369 );
1370 + PS_Helper::remember_unlock( hash_hmac( 'sha256', 'captcha-verified', get_option( 'passster_secure_key' ) ) );
1161 1371 if ( !empty( $redirect ) ) {
1162 1372 $response_data['redirect'] = $redirect;
1373 + } elseif ( $requires_reload ) {
1374 + $response_data['requires_reload'] = true;
1163 1375 } else {
1164 1376 $response_data['content'] = $content;
1165 1377 }
1166 1378 return new \WP_REST_Response($response_data, 200);