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/admin/inc/class-ps-admin.php +656 -27 4.2.114.3.16 View file →
@@ -26,31 +26,66 @@
26 26 // Check permissions before include settings.
27 27 add_action( 'admin_menu', array($this, 'remove_custom_fields_metabox') );
28 28 add_action( 'init', array($this, 'register_password_areas') );
29 29 add_filter( 'manage_protected_areas_posts_columns', array($this, 'set_area_columns') );
30 - add_filter( 'manage_post_posts_columns', array($this, 'set_post_columns') );
31 - add_filter( 'manage_page_posts_columns', array($this, 'set_post_columns') );
30 + // Register columns for all public post types (post, page, and custom).
31 + add_action( 'init', array($this, 'register_post_type_columns'), 99 );
32 32 add_action(
33 - 'manage_post_posts_custom_column',
34 - array($this, 'set_post_columns_content'),
33 + 'manage_protected_areas_posts_custom_column',
34 + array($this, 'set_area_columns_content'),
35 35 10,
36 36 2
37 37 );
38 - add_action(
39 - 'manage_page_posts_custom_column',
40 - array($this, 'set_post_columns_content'),
38 + // Modify row actions and edit links for block-sourced areas.
39 + add_filter(
40 + 'post_row_actions',
41 + array($this, 'modify_area_row_actions'),
41 42 10,
42 43 2
43 44 );
45 + add_filter(
46 + 'get_edit_post_link',
47 + array($this, 'modify_area_edit_link'),
48 + 10,
49 + 3
50 + );
51 + // Quick edit AJAX handler.
52 + add_action( 'wp_ajax_passster_quick_edit_area', array($this, 'ajax_quick_edit_area') );
53 + // Quick edit UI assets.
54 + add_action( 'admin_footer-edit.php', array($this, 'quick_edit_ui') );
55 + // Generate shortcode meta on every save so it appears in the list table.
44 56 add_action(
45 - 'manage_protected_areas_posts_custom_column',
46 - array($this, 'set_area_columns_content'),
47 - 10,
48 - 2
57 + 'save_post_protected_areas',
58 + array($this, 'save_area_shortcode'),
59 + 20,
60 + 1
49 61 );
50 62 }
51 63
52 64 /**
65 + * Register Password column for all public post types.
66 + *
67 + * @return void
68 + */
69 + public function register_post_type_columns() {
70 + $post_types = get_post_types( array(
71 + 'public' => true,
72 + ), 'names' );
73 + foreach ( $post_types as $post_type ) {
74 + if ( 'attachment' === $post_type || 'protected_areas' === $post_type ) {
75 + continue;
76 + }
77 + add_filter( "manage_{$post_type}_posts_columns", array($this, 'set_post_columns') );
78 + add_action(
79 + "manage_{$post_type}_posts_custom_column",
80 + array($this, 'set_post_columns_content'),
81 + 10,
82 + 2
83 + );
84 + }
85 + }
86 +
87 + /**
53 88 * Remove Custom Fields meta box
54 89 */
55 90 public function remove_custom_fields_metabox() {
56 91 $post_types = get_post_types( array(
@@ -60,8 +95,57 @@
60 95 remove_meta_box( 'postcustom', $post_types, 'normal' );
61 96 }
62 97
63 98 /**
99 + * Generate and save shortcode meta on post save so it appears in the list table.
100 + *
101 + * @param int $post_id Post ID.
102 + */
103 + public function save_area_shortcode( $post_id ) {
104 + if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
105 + return;
106 + }
107 + if ( wp_is_post_revision( $post_id ) ) {
108 + return;
109 + }
110 + $source = get_post_meta( $post_id, '_passster_source', true );
111 + if ( 'block' === $source ) {
112 + return;
113 + }
114 + $protection_type = get_post_meta( $post_id, 'passster_protection_type', true );
115 + $password = get_post_meta( $post_id, 'passster_password', true );
116 + $passwords = get_post_meta( $post_id, 'passster_passwords', true );
117 + $password_list = get_post_meta( $post_id, 'passster_password_list', true );
118 + $shortcode = '';
119 + switch ( $protection_type ) {
120 + case 'passwords':
121 + if ( !empty( $passwords ) ) {
122 + $shortcode = '[passster passwords="' . esc_attr( $passwords ) . '" area="' . $post_id . '"]';
123 + }
124 + break;
125 + case 'password_list':
126 + if ( !empty( $password_list ) ) {
127 + $shortcode = '[passster password_list="' . esc_attr( $password_list ) . '" area="' . $post_id . '"]';
128 + }
129 + break;
130 + case 'recaptcha':
131 + $shortcode = '[passster recaptcha="true" area="' . $post_id . '"]';
132 + break;
133 + case 'turnstile':
134 + $shortcode = '[passster turnstile="true" area="' . $post_id . '"]';
135 + break;
136 + default:
137 + if ( !empty( $password ) ) {
138 + $shortcode = '[passster password="' . esc_attr( $password ) . '" area="' . $post_id . '"]';
139 + }
140 + break;
141 + }
142 + if ( !empty( $shortcode ) ) {
143 + update_post_meta( $post_id, 'passster_area_shortcode', $shortcode );
144 + }
145 + }
146 +
147 + /**
64 148 * Register post type "protected areas"
65 149 *
66 150 * @return void
67 151 */
@@ -82,22 +166,23 @@
82 166 'not_found' => __( 'No Protected Areas found.', 'content-protector' ),
83 167 'not_found_in_trash' => __( 'No Protected Areas found in Trash.', 'content-protector' ),
84 168 );
85 169 $args = array(
86 - 'labels' => $labels,
87 - 'description' => __( 'Manageable protected areas', 'content-protector' ),
88 - 'public' => false,
89 - 'publicly_queryable' => false,
90 - 'show_ui' => true,
91 - 'show_in_menu' => 'passster',
92 - 'query_var' => true,
93 - 'rewrite' => false,
94 - 'capability_type' => 'post',
95 - 'has_archive' => false,
96 - 'hierarchical' => false,
97 - 'menu_position' => null,
98 - 'supports' => array('title', 'editor'),
99 - 'show_in_rest' => true,
170 + 'labels' => $labels,
171 + 'description' => __( 'Manageable protected areas', 'content-protector' ),
172 + 'public' => false,
173 + 'publicly_queryable' => false,
174 + 'show_ui' => true,
175 + 'show_in_menu' => 'passster',
176 + 'query_var' => true,
177 + 'rewrite' => false,
178 + 'capability_type' => 'post',
179 + 'has_archive' => false,
180 + 'hierarchical' => false,
181 + 'menu_position' => null,
182 + 'supports' => array('title', 'editor'),
183 + 'show_in_rest' => true,
184 + 'rest_controller_class' => PS_Protected_Areas_Rest_Controller::class,
100 185 );
101 186 if ( current_user_can( 'administrator' ) && (is_plugin_active( 'elementor/elementor.php' ) || is_plugin_active( 'fusion-builder/fusion-builder.php' ) || is_plugin_active( 'livecanvas/livecanvas-plugin-index.php' ) || is_plugin_active( 'divi-builder/divi-builder.php' ) || is_plugin_active( 'oxygen/functions.php' )) ) {
102 187 $args['public'] = true;
103 188 $args['publicly_queryable'] = true;
@@ -102,9 +187,9 @@
102 187 $args['public'] = true;
103 188 $args['publicly_queryable'] = true;
104 189 }
105 190 $theme = wp_get_theme();
106 - if ( current_user_can( 'administrator' ) && 'Divi' == $theme->name || 'Divi' == $theme->parent_theme ) {
191 + if ( current_user_can( 'administrator' ) && ('Divi' == $theme->name || 'Divi' == $theme->parent_theme) ) {
107 192 $args['public'] = true;
108 193 $args['publicly_queryable'] = true;
109 194 }
110 195 register_post_type( 'protected_areas', $args );
@@ -137,8 +222,21 @@
137 222 if ( $protected ) {
138 223 $protection_type = get_post_meta( $post_id, 'passster_protection_type', true );
139 224 $password = get_post_meta( $post_id, 'passster_password', true );
140 225 echo esc_html( $password );
226 + } else {
227 + // Check category-level protection.
228 + $term_data = PS_Category_Lock::get_instance()->get_protected_term_for_post( $post_id );
229 + if ( $term_data ) {
230 + $term = get_term( $term_data['term_id'] );
231 + $edit_url = get_edit_term_link( $term_data['term_id'], $term_data['taxonomy'] );
232 + echo '<span class="dashicons dashicons-category" style="font-size:14px;width:14px;height:14px;color:#007cba;vertical-align:middle;"></span> ';
233 + echo '<a href="' . esc_url( $edit_url ) . '" title="' . esc_attr__( 'Edit category protection', 'content-protector' ) . '">';
234 + echo esc_html( $term->name );
235 + echo '</a><br>';
236 + $protection_type = ( get_term_meta( $term_data['term_id'], 'passster_protection_type', true ) ?: 'password' );
237 + echo esc_html( get_term_meta( $term_data['term_id'], 'passster_password', true ) );
238 + }
141 239 }
142 240 break;
143 241 }
144 242 }
@@ -150,8 +248,9 @@
150 248 *
151 249 * @return array
152 250 */
153 251 public function set_area_columns( array $columns ) : array {
252 + $columns['source'] = __( 'Source', 'content-protector' );
154 253 $columns['shortcode'] = __( 'Shortcode', 'content-protector' );
155 254 $columns['protection-type'] = __( 'Protection Type', 'content-protector' );
156 255 $columns['password'] = __( 'Password', 'content-protector' );
157 256 $columns['redirect'] = __( 'Redirect', 'content-protector' );
@@ -168,10 +267,47 @@
168 267 * @return void
169 268 */
170 269 public function set_area_columns_content( string $column, int $post_id ) {
171 270 switch ( $column ) {
271 + case 'source':
272 + $source = get_post_meta( $post_id, '_passster_source', true );
273 + if ( 'block' === $source ) {
274 + $source_post_id = get_post_meta( $post_id, '_passster_source_post_id', true );
275 + $source_post = get_post( $source_post_id );
276 + if ( $source_post ) {
277 + $block_id = get_post_meta( $post_id, '_passster_block_id', true );
278 + $params = array(
279 + 'post' => absint( $source_post_id ),
280 + 'action' => 'edit',
281 + );
282 + if ( $block_id ) {
283 + $params['passster_focus_block'] = $block_id;
284 + }
285 + $edit_link = add_query_arg( $params, admin_url( 'post.php' ) );
286 + echo '<span class="dashicons dashicons-block-default" style="color: #007cba;"></span> ';
287 + printf(
288 + '<a href="%s" title="%s">%s</a>',
289 + esc_url( $edit_link ),
290 + esc_attr__( 'Edit source post', 'content-protector' ),
291 + esc_html( $source_post->post_title )
292 + );
293 + } else {
294 + echo '<span class="dashicons dashicons-block-default"></span> ';
295 + esc_html_e( 'Block (orphaned)', 'content-protector' );
296 + }
297 + } else {
298 + echo '<span class="dashicons dashicons-admin-page" style="color: #757575;"></span> ';
299 + esc_html_e( 'Standalone', 'content-protector' );
300 + }
301 + break;
172 302 case 'shortcode':
303 + $source = get_post_meta( $post_id, '_passster_source', true );
173 304 $shortcode = get_post_meta( $post_id, 'passster_area_shortcode', true );
305 + // Block-sourced areas don't need shortcode (embedded in post).
306 + if ( 'block' === $source ) {
307 + echo '<span style="color: #757575;">' . esc_html__( 'Embedded in block', 'content-protector' ) . '</span>';
308 + break;
309 + }
174 310 ?>
175 311 <?php
176 312 if ( !empty( $shortcode ) ) {
177 313 ?>
@@ -247,9 +383,10 @@
247 383 'password' => __( 'Password', 'content-protector' ),
248 384 'password_list' => __( 'Password List', 'content-protector' ),
249 385 'password_lists' => __( 'Password Lists', 'content-protector' ),
250 386 'passwords' => __( 'Passwords', 'content-protector' ),
251 - 'recaptcha' => __( 'ReCaptcha', 'content-protector' ),
387 + 'recaptcha' => __( 'reCaptcha', 'content-protector' ),
388 + 'turnstile' => __( 'Turnstile', 'content-protector' ),
252 389 );
253 390 echo esc_html( $protection_types[$type] );
254 391 break;
255 392 case 'password':
@@ -259,8 +396,9 @@
259 396 'password_list' => get_post_meta( $post_id, 'passster_password_list', true ),
260 397 'password_lists' => get_post_meta( $post_id, 'passster_password_lists', true ),
261 398 'passwords' => get_post_meta( $post_id, 'passster_passwords', true ),
262 399 'recaptcha' => '-',
400 + 'turnstile' => '-',
263 401 );
264 402 // Build the edit link if it's a password list.
265 403 if ( 'password_list' === $type ) {
266 404 $edit_link = get_edit_post_link( $protection_types[$type] );
@@ -311,7 +449,498 @@
311 449 echo '-';
312 450 }
313 451 break;
314 452 }
453 + }
454 +
455 + /**
456 + * Output quick edit UI HTML and JS.
457 + *
458 + * @return void
459 + */
460 + public function quick_edit_ui() {
461 + $screen = get_current_screen();
462 + if ( !$screen || 'protected_areas' !== $screen->post_type ) {
463 + return;
464 + }
465 + // Get password lists for dropdown.
466 + $password_lists = get_posts( array(
467 + 'post_type' => 'password_lists',
468 + 'posts_per_page' => -1,
469 + 'post_status' => 'publish',
470 + ) );
471 + ?>
472 + <div id="passster-quick-edit-modal" style="display: none;">
473 + <div class="passster-quick-edit-backdrop"></div>
474 + <div class="passster-quick-edit-panel">
475 + <div class="passster-quick-edit-header">
476 + <h3><?php
477 + esc_html_e( 'Quick Settings', 'content-protector' );
478 + ?></h3>
479 + <button type="button" class="passster-quick-edit-close">&times;</button>
480 + </div>
481 + <div class="passster-quick-edit-body">
482 + <input type="hidden" id="passster-qe-area-id" value="">
483 + <input type="hidden" id="passster-qe-nonce" value="">
484 +
485 + <p class="passster-qe-field">
486 + <label for="passster-qe-protection-type"><?php
487 + esc_html_e( 'Protection Type', 'content-protector' );
488 + ?></label>
489 + <select id="passster-qe-protection-type">
490 + <option value="password"><?php
491 + esc_html_e( 'Single Password', 'content-protector' );
492 + ?></option>
493 + <option value="passwords"><?php
494 + esc_html_e( 'Multiple Passwords', 'content-protector' );
495 + ?></option>
496 + <option value="password_list"><?php
497 + esc_html_e( 'Password List', 'content-protector' );
498 + ?></option>
499 + </select>
500 + </p>
501 +
502 + <p class="passster-qe-field passster-qe-password-field">
503 + <label for="passster-qe-password"><?php
504 + esc_html_e( 'Password', 'content-protector' );
505 + ?></label>
506 + <input type="text" id="passster-qe-password" value="">
507 + </p>
508 +
509 + <p class="passster-qe-field passster-qe-passwords-field" style="display: none;">
510 + <label for="passster-qe-passwords"><?php
511 + esc_html_e( 'Passwords (one per line)', 'content-protector' );
512 + ?></label>
513 + <textarea id="passster-qe-passwords" rows="4"></textarea>
514 + </p>
515 +
516 + <p class="passster-qe-field passster-qe-list-field" style="display: none;">
517 + <label for="passster-qe-password-list"><?php
518 + esc_html_e( 'Password List', 'content-protector' );
519 + ?></label>
520 + <select id="passster-qe-password-list">
521 + <option value="0"><?php
522 + esc_html_e( '— Select —', 'content-protector' );
523 + ?></option>
524 + <?php
525 + foreach ( $password_lists as $list ) {
526 + ?>
527 + <option value="<?php
528 + echo esc_attr( $list->ID );
529 + ?>"><?php
530 + echo esc_html( $list->post_title );
531 + ?></option>
532 + <?php
533 + }
534 + ?>
535 + </select>
536 + </p>
537 +
538 + <p class="passster-qe-source-info" style="display: none;">
539 + <em><?php
540 + esc_html_e( 'This area is synced from a block. Changes will update the block.', 'content-protector' );
541 + ?></em>
542 + </p>
543 + </div>
544 + <div class="passster-quick-edit-footer">
545 + <button type="button" class="button passster-quick-edit-cancel"><?php
546 + esc_html_e( 'Cancel', 'content-protector' );
547 + ?></button>
548 + <button type="button" class="button button-primary passster-quick-edit-save"><?php
549 + esc_html_e( 'Save', 'content-protector' );
550 + ?></button>
551 + <span class="spinner"></span>
552 + </div>
553 + </div>
554 + </div>
555 +
556 + <style>
557 + .passster-quick-edit-backdrop {
558 + position: fixed;
559 + top: 0;
560 + left: 0;
561 + right: 0;
562 + bottom: 0;
563 + background: rgba(0, 0, 0, 0.5);
564 + z-index: 100000;
565 + }
566 + .passster-quick-edit-panel {
567 + position: fixed;
568 + top: 50%;
569 + left: 50%;
570 + transform: translate(-50%, -50%);
571 + background: #fff;
572 + border-radius: 8px;
573 + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
574 + width: 400px;
575 + max-width: 90vw;
576 + z-index: 100001;
577 + }
578 + .passster-quick-edit-header {
579 + display: flex;
580 + justify-content: space-between;
581 + align-items: center;
582 + padding: 16px 20px;
583 + border-bottom: 1px solid #ddd;
584 + }
585 + .passster-quick-edit-header h3 {
586 + margin: 0;
587 + font-size: 16px;
588 + }
589 + .passster-quick-edit-close {
590 + background: none;
591 + border: none;
592 + font-size: 24px;
593 + cursor: pointer;
594 + color: #666;
595 + padding: 0;
596 + line-height: 1;
597 + }
598 + .passster-quick-edit-close:hover {
599 + color: #d63638;
600 + }
601 + .passster-quick-edit-body {
602 + padding: 20px;
603 + }
604 + .passster-qe-field {
605 + margin-bottom: 16px;
606 + }
607 + .passster-qe-field label {
608 + display: block;
609 + margin-bottom: 4px;
610 + font-weight: 600;
611 + }
612 + .passster-qe-field input,
613 + .passster-qe-field select,
614 + .passster-qe-field textarea {
615 + width: 100%;
616 + }
617 + .passster-qe-source-info {
618 + background: #f0f6fc;
619 + padding: 10px;
620 + border-radius: 4px;
621 + color: #1e1e1e;
622 + }
623 + .passster-quick-edit-footer {
624 + display: flex;
625 + justify-content: flex-end;
626 + align-items: center;
627 + gap: 8px;
628 + padding: 16px 20px;
629 + border-top: 1px solid #ddd;
630 + background: #f6f7f7;
631 + border-radius: 0 0 8px 8px;
632 + }
633 + .passster-quick-edit-footer .spinner {
634 + float: none;
635 + margin: 0;
636 + }
637 + </style>
638 +
639 + <script>
640 + jQuery(function($) {
641 + var $modal = $('#passster-quick-edit-modal');
642 + var $typeSelect = $('#passster-qe-protection-type');
643 +
644 + // Toggle fields based on protection type
645 + function toggleFields() {
646 + var type = $typeSelect.val();
647 + $('.passster-qe-password-field').toggle(type === 'password');
648 + $('.passster-qe-passwords-field').toggle(type === 'passwords');
649 + $('.passster-qe-list-field').toggle(type === 'password_list');
650 + }
651 +
652 + $typeSelect.on('change', toggleFields);
653 +
654 + // Open modal
655 + $(document).on('click', '.passster-quick-settings', function(e) {
656 + e.preventDefault();
657 + var areaId = $(this).data('area-id');
658 + var nonce = $(this).data('nonce');
659 +
660 + $('#passster-qe-area-id').val(areaId);
661 + $('#passster-qe-nonce').val(nonce);
662 +
663 + // Load current settings
664 + $.post(ajaxurl, {
665 + action: 'passster_quick_edit_area',
666 + area_id: areaId,
667 + nonce: nonce,
668 + edit_action: 'get'
669 + }, function(response) {
670 + if (response.success) {
671 + var data = response.data;
672 + $typeSelect.val(data.protection_type);
673 + $('#passster-qe-password').val(data.password || '');
674 + $('#passster-qe-passwords').val(data.passwords || '');
675 + $('#passster-qe-password-list').val(data.password_list || 0);
676 + $('.passster-qe-source-info').toggle(data.source === 'block');
677 + toggleFields();
678 + $modal.show();
679 + }
680 + });
681 + });
682 +
683 + // Close modal
684 + $modal.on('click', '.passster-quick-edit-close, .passster-quick-edit-cancel, .passster-quick-edit-backdrop', function() {
685 + $modal.hide();
686 + });
687 +
688 + // Save
689 + $modal.on('click', '.passster-quick-edit-save', function() {
690 + var $btn = $(this);
691 + var $spinner = $modal.find('.spinner');
692 +
693 + $btn.prop('disabled', true);
694 + $spinner.addClass('is-active');
695 +
696 + $.post(ajaxurl, {
697 + action: 'passster_quick_edit_area',
698 + area_id: $('#passster-qe-area-id').val(),
699 + nonce: $('#passster-qe-nonce').val(),
700 + edit_action: 'save',
701 + protection_type: $typeSelect.val(),
702 + password: $('#passster-qe-password').val(),
703 + passwords: $('#passster-qe-passwords').val(),
704 + password_list: $('#passster-qe-password-list').val()
705 + }, function(response) {
706 + $btn.prop('disabled', false);
707 + $spinner.removeClass('is-active');
708 +
709 + if (response.success) {
710 + $modal.hide();
711 + location.reload();
712 + } else {
713 + alert(response.data.message || 'Error saving settings.');
714 + }
715 + });
716 + });
717 +
718 + // ESC to close
719 + $(document).on('keydown', function(e) {
720 + if (e.key === 'Escape' && $modal.is(':visible')) {
721 + $modal.hide();
722 + }
723 + });
724 + });
725 + </script>
726 + <?php
727 + }
728 +
729 + /**
730 + * Modify row actions for Protected Areas.
731 + * Block-sourced areas: Edit goes to source post, add Quick Settings.
732 + *
733 + * @param array $actions Row actions.
734 + * @param WP_Post $post Post object.
735 + *
736 + * @return array Modified actions.
737 + */
738 + public function modify_area_row_actions( $actions, $post ) {
739 + if ( 'protected_areas' !== $post->post_type ) {
740 + return $actions;
741 + }
742 + $source = get_post_meta( $post->ID, '_passster_source', true );
743 + if ( 'block' === $source ) {
744 + $source_post_id = get_post_meta( $post->ID, '_passster_source_post_id', true );
745 + $source_post = get_post( $source_post_id );
746 + if ( $source_post ) {
747 + $block_id = get_post_meta( $post->ID, '_passster_block_id', true );
748 + $edit_url = get_edit_post_link( $source_post_id );
749 + if ( $block_id && $edit_url ) {
750 + $edit_url = add_query_arg( 'passster_focus_block', rawurlencode( $block_id ), $edit_url );
751 + }
752 + // Change Edit to go to source post with the specific block focused.
753 + $actions['edit'] = sprintf(
754 + '<a href="%s" aria-label="%s">%s</a>',
755 + esc_url( $edit_url ),
756 + /* translators: %s: Post title */
757 + esc_attr( sprintf( __( 'Edit block in &#8220;%s&#8221;', 'content-protector' ), $source_post->post_title ) ),
758 + __( 'Edit Block', 'content-protector' )
759 + );
760 + }
761 + // Remove trash for block-sourced (they're synced automatically).
762 + unset($actions['trash']);
763 + }
764 + // Add Quick Settings for all areas.
765 + $actions['quick_settings'] = sprintf(
766 + '<a href="#" class="passster-quick-settings" data-area-id="%d" data-nonce="%s">%s</a>',
767 + $post->ID,
768 + wp_create_nonce( 'passster_quick_edit_' . $post->ID ),
769 + __( 'Quick Settings', 'content-protector' )
770 + );
771 + return $actions;
772 + }
773 +
774 + /**
775 + * Modify edit post link for block-sourced areas.
776 + *
777 + * @param string $link Edit link.
778 + * @param int $post_id Post ID.
779 + * @param string $context Context.
780 + *
781 + * @return string Modified link.
782 + */
783 + public function modify_area_edit_link( $link, $post_id, $context ) {
784 + $post = get_post( $post_id );
785 + if ( !$post || 'protected_areas' !== $post->post_type ) {
786 + return $link;
787 + }
788 + $source = get_post_meta( $post_id, '_passster_source', true );
789 + if ( 'block' === $source ) {
790 + $source_post_id = get_post_meta( $post_id, '_passster_source_post_id', true );
791 + if ( $source_post_id ) {
792 + $block_id = get_post_meta( $post_id, '_passster_block_id', true );
793 + $params = array(
794 + 'post' => absint( $source_post_id ),
795 + 'action' => 'edit',
796 + );
797 + if ( $block_id ) {
798 + $params['passster_focus_block'] = $block_id;
799 + }
800 + $edit_link = add_query_arg( $params, admin_url( 'post.php' ) );
801 + return ( 'display' === $context ? esc_url( $edit_link ) : $edit_link );
802 + }
803 + }
804 + return $link;
805 + }
806 +
807 + /**
808 + * AJAX handler for quick edit area settings.
809 + *
810 + * @return void
811 + */
812 + public function ajax_quick_edit_area() {
813 + $area_id = ( isset( $_POST['area_id'] ) ? absint( $_POST['area_id'] ) : 0 );
814 + $nonce = ( isset( $_POST['nonce'] ) ? sanitize_text_field( wp_unslash( $_POST['nonce'] ) ) : '' );
815 + $action = ( isset( $_POST['edit_action'] ) ? sanitize_text_field( wp_unslash( $_POST['edit_action'] ) ) : 'get' );
816 + if ( !wp_verify_nonce( $nonce, 'passster_quick_edit_' . $area_id ) ) {
817 + wp_send_json_error( array(
818 + 'message' => __( 'Security check failed.', 'content-protector' ),
819 + ) );
820 + }
821 + if ( !current_user_can( 'edit_post', $area_id ) ) {
822 + wp_send_json_error( array(
823 + 'message' => __( 'Permission denied.', 'content-protector' ),
824 + ) );
825 + }
826 + $area = get_post( $area_id );
827 + if ( !$area || 'protected_areas' !== $area->post_type ) {
828 + wp_send_json_error( array(
829 + 'message' => __( 'Invalid area.', 'content-protector' ),
830 + ) );
831 + }
832 + if ( 'get' === $action ) {
833 + // Return current settings.
834 + $data = array(
835 + 'area_id' => $area_id,
836 + 'title' => $area->post_title,
837 + 'protection_type' => ( get_post_meta( $area_id, 'passster_protection_type', true ) ?: 'password' ),
838 + 'password' => get_post_meta( $area_id, 'passster_password', true ),
839 + 'passwords' => get_post_meta( $area_id, 'passster_passwords', true ),
840 + 'password_list' => get_post_meta( $area_id, 'passster_password_list', true ),
841 + 'source' => get_post_meta( $area_id, '_passster_source', true ),
842 + );
843 + wp_send_json_success( $data );
844 + }
845 + if ( 'save' === $action ) {
846 + // Save settings.
847 + $protection_type = ( isset( $_POST['protection_type'] ) ? sanitize_text_field( wp_unslash( $_POST['protection_type'] ) ) : 'password' );
848 + update_post_meta( $area_id, 'passster_protection_type', $protection_type );
849 + switch ( $protection_type ) {
850 + case 'password':
851 + $password = ( isset( $_POST['password'] ) ? sanitize_text_field( wp_unslash( $_POST['password'] ) ) : '' );
852 + update_post_meta( $area_id, 'passster_password', $password );
853 + break;
854 + case 'passwords':
855 + $passwords = ( isset( $_POST['passwords'] ) ? sanitize_textarea_field( wp_unslash( $_POST['passwords'] ) ) : '' );
856 + update_post_meta( $area_id, 'passster_passwords', $passwords );
857 + break;
858 + case 'password_list':
859 + $password_list = ( isset( $_POST['password_list'] ) ? absint( $_POST['password_list'] ) : 0 );
860 + update_post_meta( $area_id, 'passster_password_list', $password_list );
861 + break;
862 + }
863 + // If block-sourced, sync back to block.
864 + $source = get_post_meta( $area_id, '_passster_source', true );
865 + if ( 'block' === $source ) {
866 + $source_post_id = get_post_meta( $area_id, '_passster_source_post_id', true );
867 + $block_id = get_post_meta( $area_id, '_passster_block_id', true );
868 + if ( $source_post_id && $block_id ) {
869 + $this->sync_settings_to_block( $source_post_id, $block_id, $area_id );
870 + }
871 + }
872 + wp_send_json_success( array(
873 + 'message' => __( 'Settings saved.', 'content-protector' ),
874 + ) );
875 + }
876 + wp_send_json_error( array(
877 + 'message' => __( 'Invalid action.', 'content-protector' ),
878 + ) );
879 + }
880 +
881 + /**
882 + * Sync settings from Protected Area back to the block.
883 + *
884 + * @param int $post_id Source post ID.
885 + * @param string $block_id Block ID.
886 + * @param int $area_id Protected Area ID.
887 + *
888 + * @return bool Success.
889 + */
890 + private function sync_settings_to_block( $post_id, $block_id, $area_id ) {
891 + $post = get_post( $post_id );
892 + if ( !$post ) {
893 + return false;
894 + }
895 + $content = $post->post_content;
896 + $blocks = parse_blocks( $content );
897 + $protection_type = get_post_meta( $area_id, 'passster_protection_type', true );
898 + $password = get_post_meta( $area_id, 'passster_password', true );
899 + $passwords = get_post_meta( $area_id, 'passster_passwords', true );
900 + $password_list = get_post_meta( $area_id, 'passster_password_list', true );
901 + $updated_blocks = $this->update_block_settings( $blocks, $block_id, array(
902 + 'protectionType' => $protection_type,
903 + 'password' => $password,
904 + 'passwords' => $passwords,
905 + 'passwordList' => (int) $password_list,
906 + ) );
907 + $new_content = serialize_blocks( $updated_blocks );
908 + // Update post without triggering our sync hook.
909 + remove_action( 'save_post', array(\passster\PS_Block_Editor::get_instance(), 'sync_protected_content_blocks'), 10 );
910 + wp_update_post( array(
911 + 'ID' => $post_id,
912 + 'post_content' => $new_content,
913 + ) );
914 + add_action(
915 + 'save_post',
916 + array(\passster\PS_Block_Editor::get_instance(), 'sync_protected_content_blocks'),
917 + 10,
918 + 2
919 + );
920 + return true;
921 + }
922 +
923 + /**
924 + * Recursively update block settings.
925 + *
926 + * @param array $blocks Blocks array.
927 + * @param string $block_id Target block ID.
928 + * @param array $settings New settings.
929 + *
930 + * @return array Updated blocks.
931 + */
932 + private function update_block_settings( $blocks, $block_id, $settings ) {
933 + foreach ( $blocks as &$block ) {
934 + if ( 'passster/content-lock' === $block['blockName'] ) {
935 + if ( isset( $block['attrs']['blockId'] ) && $block['attrs']['blockId'] === $block_id ) {
936 + $block['attrs'] = array_merge( $block['attrs'], $settings );
937 + }
938 + }
939 + if ( !empty( $block['innerBlocks'] ) ) {
940 + $block['innerBlocks'] = $this->update_block_settings( $block['innerBlocks'], $block_id, $settings );
941 + }
942 + }
943 + return $blocks;
315 944 }
316 945
317 946 }