PluginProbe
Post Grid Gutenberg Blocks – PostX / 5.1.0
Post Grid Gutenberg Blocks – PostX v5.1.0
5.1.1 5.1.0 5.0.41 5.0.39 5.0.38 5.0.37 5.0.36 5.0.35 5.0.34 5.0.33 5.0.32 5.0.31 5.0.30 5.0.29 5.0.28 5.0.27 5.0.26 5.0.25 5.0.23 5.0.24 5.0.22 5.0.21 5.0.20 5.0.19 5.0.18 All 240 releases
← All changes | classes/Blocks.php +99 -15 5.0.19 → 5.1.0 View file →
@@ -1,5 +1,8 @@
1 1 <?php
2 +// phpcs:ignore
3 +// @codingStandardsIgnoreFile
4 +// @ignoreWarnings
2 5 /**
3 6 * Require All Blocks and handle block ajax action
4 7 *
5 8 * @package ULTP\Blocks
@@ -6,9 +9,8 @@
6 9 * @since 4.1.11
7 10 */
8 11
9 12 namespace ULTP;
10 -
11 13 defined( 'ABSPATH' ) || exit;
12 14
13 15 /**
14 16 * Blocks class.
@@ -37,10 +39,12 @@
37 39 add_action( 'wp_ajax_nopriv_ultp_pagination', array( $this, 'ultp_pagination_callback' ) ); // Page Number AJAX Call Logout User .
38 40
39 41 add_action( 'wp_ajax_ultp_share_count', array( $this, 'ultp_share_count_callback' ) ); // share Count save .
40 42 add_action( 'wp_ajax_nopriv_ultp_share_count', array( $this, 'ultp_share_count_callback' ) ); // share Count save .
41 - add_action( 'wp_ajax_ultp_get_nonce', array( $this, 'ultp_get_nonce_callback' ) ); // Nonce Generating Callback
42 - add_action( 'wp_ajax_nopriv_ultp_get_nonce', array( $this, 'ultp_get_nonce_callback' ) ); // Nonce Generating Callback
43 + // Public by design — see ultp_get_nonce_callback() for why, and for what
44 + // 'ultp-public-nonce' does and does not protect.
45 + add_action( 'wp_ajax_ultp_get_nonce', array( $this, 'ultp_get_nonce_callback' ) );
46 + add_action( 'wp_ajax_nopriv_ultp_get_nonce', array( $this, 'ultp_get_nonce_callback' ) );
43 47 }
44 48
45 49 /**
46 50 * Require Blocks
@@ -225,9 +229,9 @@
225 229 );
226 230 $value['attrs']['queryExclude'] = wp_json_encode( $queryArr );
227 231 }
228 232 $attr = array_merge( $attr, $value['attrs'] );
229 - echo $objectBlock->content($attr, true); //phpcs:ignore
233 + echo $objectBlock->content( $attr, true ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Block render output; each block escapes its own markup.
230 234 die();
231 235 }
232 236 }
233 237 if ( ! empty( $value['innerBlocks'] ) ) {
@@ -244,9 +248,9 @@
244 248 *
245 249 * @return STRING The AJAX response.
246 250 */
247 251 public function ultp_next_prev_callback() {
248 - if ( ! ( isset( $_REQUEST['wpnonce'] ) && wp_verify_nonce( sanitize_key( wp_unslash( $_REQUEST['wpnonce'] ) ), 'ultp-nonce' ) ) ) {
252 + if ( ! $this->verify_public_nonce() ) {
249 253 return;
250 254 }
251 255
252 256 $paged = isset( $_POST['paged'] ) ? sanitize_text_field( $_POST['paged'] ) : '';
@@ -338,9 +342,9 @@
338 342 *
339 343 * @return STRING The AJAX response.
340 344 */
341 345 public function ultp_filter_callback() {
342 - if ( ! ( isset( $_REQUEST['wpnonce'] ) && wp_verify_nonce( sanitize_key( wp_unslash( $_REQUEST['wpnonce'] ) ), 'ultp-nonce' ) ) ) {
346 + if ( ! $this->verify_public_nonce() ) {
343 347 return;
344 348 }
345 349
346 350 $taxtype = isset( $_POST['taxtype'] ) ? sanitize_text_field( $_POST['taxtype'] ) : '';
@@ -378,9 +382,9 @@
378 382 *
379 383 * @return STRING The AJAX response.
380 384 */
381 385 public function ultp_adv_filter_callback() {
382 - if ( ! ( isset( $_REQUEST['wpnonce'] ) && wp_verify_nonce( sanitize_key( wp_unslash( $_REQUEST['wpnonce'] ) ), 'ultp-nonce' ) ) ) {
386 + if ( ! $this->verify_public_nonce() ) {
383 387 return;
384 388 }
385 389
386 390 $blockId = isset( $_POST['blockId'] ) ? sanitize_text_field( $_POST['blockId'] ) : '';
@@ -436,9 +440,9 @@
436 440 *
437 441 * @return STRING The AJAX response.
438 442 */
439 443 public function ultp_pagination_callback() {
440 - if ( ! ( isset( $_REQUEST['wpnonce'] ) && wp_verify_nonce( sanitize_key( wp_unslash( $_REQUEST['wpnonce'] ) ), 'ultp-nonce' ) ) ) {
444 + if ( ! $this->verify_public_nonce() ) {
441 445 return;
442 446 }
443 447
444 448 $paged = isset( $_POST['paged'] ) ? sanitize_text_field( $_POST['paged'] ) : '';
@@ -509,9 +513,9 @@
509 513 *
510 514 * @return void
511 515 */
512 516 public function ultp_share_count_callback() {
513 - if ( ! ( isset( $_REQUEST['wpnonce'] ) && wp_verify_nonce( sanitize_key( wp_unslash( $_REQUEST['wpnonce'] ) ), 'ultp-nonce' ) ) ) {
517 + if ( ! $this->verify_public_nonce() ) {
514 518 return;
515 519 }
516 520
517 521 $post_id = isset( $_POST['postId'] ) ? absint( $_POST['postId'] ) : 0;
@@ -592,8 +596,19 @@
592 596 * @return ARRAY
593 597 */
594 598 public function filter_content_return( $blocks, $blockId, $blockRaw, $blockName, $taxtype, $taxonomy, $postId, &$toReturn, $widgetBlockId = '', $adv_filter_data = array(), $ultp_uniqueIds = array(), $ultp_current_unique_posts = array() ) {
595 599 foreach ( $blocks as $key => $value ) {
600 + if ( ( $value['blockName'] ?? '' ) === 'core/shortcode' ) {
601 + if ( preg_match( '/id="([^"]+)"/', $value['innerHTML'] ?? '', $matches ) ) {
602 + $template_post = get_post( $matches[1] );
603 + if ( $template_post && ! empty( $template_post->post_content ) ) {
604 + $template_blocks = parse_blocks( $template_post->post_content );
605 + // recurse into the expanded blocks instead
606 + $this->filter_content_return( $template_blocks, $blockId, $blockRaw, $blockName, $taxtype, $taxonomy, $postId, $toReturn, $widgetBlockId, $adv_filter_data, $ultp_uniqueIds, $ultp_current_unique_posts );
607 + continue; // skip the shortcode block itself
608 + }
609 + }
610 + }
596 611 if ( $blockName == $value['blockName'] ) {
597 612 if ( $value['attrs']['blockId'] == $blockId ) {
598 613 $objName = str_replace( ' ', '_', ucwords( str_replace( array( 'ultimate-post/', '-' ), array( '', ' ' ), $blockName ) ) );
599 614 $new_obj = '\ULTP\blocks\\' . $objName;
@@ -642,18 +657,21 @@
642 657 $filter_attributes['queryTax'] = $taxtype;
643 658
644 659 if ( $filter_attributes['isAdv'] ) {
645 660 $filter_attributes['queryAuthor'] = $adv_filter_data['author'];
646 - $filter_attributes['queryOrderBy'] = ! empty( $adv_filter_data['orderby'] ) ? $adv_filter_data['orderby'] : $value['attrs']['queryOrderBy'];
647 661 $filter_attributes['queryOrder'] = $adv_filter_data['order'];
648 662 $filter_attributes['querySearch'] = $adv_filter_data['search'];
649 663 $filter_attributes['queryQuick'] = $adv_filter_data['adv_sort'];
664 + $filter_attributes['queryOrderBy'] =
665 + ! empty( $adv_filter_data['orderby'] ) ? $adv_filter_data['orderby'] : ( $value['attrs']['queryOrderBy'] ?? '' );
650 666 }
651 -
667 + $pagination = $this->pagination_for_filter( $attr, $postId, $blockRaw, $filter_attributes );
652 668 $toReturn = array(
653 669 'blocks' => $objectBlock->content( $attr, true ),
654 670 'notFound' => isset( $attr['notFoundMessage'] ) ? $attr['notFoundMessage'] : '',
655 - 'pagination' => $this->pagination_for_filter( $attr, $postId, $blockRaw, $filter_attributes ),
671 + 'pagination' => $pagination['pagination_content'],
672 + 'data_attrs' => $pagination['data_attrs'],
673 + 'data_all_attrs' => $pagination['data_attrs'],
656 674 'paginationType' => $attr['paginationType'],
657 675 'paginationShow' => $attr['paginationShow'],
658 676 );
659 677 }
@@ -709,15 +727,81 @@
709 727
710 728 $wraper_after .= '</div>';
711 729 }
712 730 wp_reset_query();
731 +
732 + // queryQuick
733 + return array(
734 + 'pagination_content' => $wraper_after,
735 + 'data_attrs' => wp_json_encode( array(
736 + 'paged' => 1,
737 + 'blockid' => $attr['blockId'],
738 + 'postid' => $postId,
739 + 'pages' => $pageNum,
740 + 'blockname' => $blockRaw,
741 + 'filter_value' => isset( $filter_attributes['queryTaxValue'] ) ? $filter_attributes['queryTaxValue'] : '',
742 + 'filter_type' => isset( $filter_attributes['queryTax'] ) ? $filter_attributes['queryTax'] : '',
743 + 'filter_author' => isset( $filter_attributes['queryAuthor'] ) ? $filter_attributes['queryAuthor'] : '',
744 + 'filter_order' => isset( $filter_attributes['queryOrder'] ) ? $filter_attributes['queryOrder'] : 'DESC',
745 + 'filter_orderby' => isset( $filter_attributes['queryOrderBy'] ) ? $filter_attributes['queryOrderBy'] : '',
746 + 'filter_search' => isset( $filter_attributes['querySearch'] ) ? $filter_attributes['querySearch'] : '',
747 + 'filter_queryQuick' => isset( $filter_attributes['queryQuick'] ) ? $filter_attributes['queryQuick'] : '',
748 + 'filter_for' => 'ultp-block-' . $attr['blockId'],
749 + )),
750 + );
751 + }
713 752
714 - return $wraper_after;
753 + /**
754 + * Verify the front-end AJAX nonce, or end the request.
755 + *
756 + * Fails with a machine-readable 403 rather than the bare `return` these
757 + * handlers used to do. A bare return lets admin-ajax emit "0" at HTTP 200,
758 + * which the client cannot tell apart from a real response — ultp_next_prev's
759 + * success handler treated that "0" as markup and rendered it into the block.
760 + *
761 + * The 'ultp_invalid_nonce' code is a contract with assets/js/ultp.js: on
762 + * seeing it the client discards its cached nonce, fetches a fresh one and
763 + * retries once. A nonce legitimately expires while a tab is open (WordPress
764 + * nonces last 12-24h, tabs last longer), so this is a routine condition and
765 + * not an attack signal. See ultp_get_nonce_callback() for what this nonce
766 + * does and does not protect.
767 + *
768 + * @since v.5.1.0
769 + *
770 + * @return BOOLEAN True when the nonce is valid; otherwise the request ends.
771 + */
772 + private function verify_public_nonce() {
773 + if ( isset( $_REQUEST['wpnonce'] ) && wp_verify_nonce( sanitize_key( wp_unslash( $_REQUEST['wpnonce'] ) ), 'ultp-public-nonce' ) ) {
774 + return true;
775 + }
776 +
777 + wp_send_json_error(
778 + array(
779 + 'code' => 'ultp_invalid_nonce',
780 + 'message' => __( 'Security token expired. Please try again.', 'ultimate-post' ),
781 + ),
782 + 403
783 + );
784 +
785 + return false;
715 786 }
716 787
717 788 /**
718 - * Nonce Generation Callback
789 + * Vends the front-end AJAX nonce.
719 790 *
791 + * Public by design: full-page caching makes a nonce embedded in HTML stale for
792 + * most visitors, so front-end blocks fetch one at runtime instead.
793 + *
794 + * SECURITY: because this endpoint is public, 'ultp-public-nonce' is NOT a CSRF
795 + * control on the front end — anyone can request one. It is a request-shape
796 + * check only. Any nopriv handler that changes state MUST carry its own
797 + * authorisation; see ultp_share_count_callback() for the pattern (post status,
798 + * capability and rate-limit checks, none of which depend on the nonce).
799 + *
800 + * This endpoint vends the PUBLIC nonce only. Admin routes verify the separate
801 + * 'ultp-admin-nonce', which is never vended here and is always paired with a
802 + * capability check. Do not merge the two.
803 + *
720 804 * @since v.5.0.6
721 805 *
722 806 * @return STRING The AJAX response.
723 807 */
@@ -724,9 +808,9 @@
724 808 public function ultp_get_nonce_callback() {
725 809 nocache_headers();
726 810 wp_send_json_success(
727 811 array(
728 - 'nonce' => wp_create_nonce( 'ultp-nonce' ),
812 + 'nonce' => wp_create_nonce( 'ultp-public-nonce' ),
729 813 )
730 814 );
731 815 }
732 816 }