PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3
Jetpack – WP Security, Backup, Speed, & Growth v16.3
16.3 16.3-beta 16.3-a.5 16.3-a.7 16.3-a.3 16.3-a.1 16.2 16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 All 508 releases
← All changes | extensions/blocks/goodreads/render.php +136 -0 16.2-beta → 16.3 View file →
@@ -1,0 +1,136 @@
1 +<?php
2 +/**
3 + * Goodreads block render implementation.
4 + *
5 + * Loaded lazily from goodreads.php only when the block is rendered, to keep
6 + * the render body out of the eager front-end PHP/opcache footprint.
7 + *
8 + * @package automattic/jetpack
9 + */
10 +
11 +namespace Automattic\Jetpack\Extensions\Goodreads;
12 +
13 +use Automattic\Jetpack\Blocks;
14 +use Jetpack_Gutenberg;
15 +
16 +if ( ! defined( 'ABSPATH' ) ) {
17 + exit( 0 );
18 +}
19 +
20 +/**
21 + * Validates a Goodreads widget script URL.
22 + *
23 + * Accepts only URLs shaped like those generated by createGoodreadsEmbedLink()
24 + * in utils.js — keep the two in sync when either side changes.
25 + *
26 + * @param mixed $url URL to validate.
27 + *
28 + * @return string Canonical URL rebuilt from the validated components, or an empty string when the URL is not allowed.
29 + */
30 +function get_validated_script_url( $url ) {
31 + if ( ! is_string( $url ) ) {
32 + return '';
33 + }
34 +
35 + // A link whose query separators are HTML-encoded parses as `amp;`-prefixed parameter
36 + // names and fails the allowlist below. The decode table cannot produce a path or
37 + // authority delimiter, so normalizing here does not widen what is accepted.
38 + $url = wp_specialchars_decode( $url );
39 +
40 + if ( str_contains( $url, '\\' ) ) {
41 + return '';
42 + }
43 +
44 + $parsed = wp_parse_url( esc_url_raw( $url, array( 'https' ) ) );
45 + $encoded_path = is_array( $parsed ) ? $parsed['path'] ?? '' : '';
46 + $path = rawurldecode( $encoded_path );
47 + $has_encoded_separator = 1 === preg_match( '~%(?:2f|5c)~i', $encoded_path );
48 +
49 + if (
50 + ! is_array( $parsed )
51 + || empty( $parsed['scheme'] )
52 + || 'https' !== strtolower( $parsed['scheme'] )
53 + || empty( $parsed['host'] )
54 + || 'www.goodreads.com' !== strtolower( $parsed['host'] )
55 + || isset( $parsed['user'] )
56 + || isset( $parsed['pass'] )
57 + || isset( $parsed['port'] )
58 + || isset( $parsed['fragment'] )
59 + || empty( $parsed['query'] )
60 + || $has_encoded_separator
61 + || str_contains( $path, '\\' )
62 + ) {
63 + return '';
64 + }
65 +
66 + // Only the documented widget endpoints: a numeric Goodreads ID and a non-empty title.
67 + // Goodreads treats literal slashes as part of the title, so allow them while
68 + // rejecting dot segments that a URL parser could normalize outside this route.
69 + if ( 1 !== preg_match( '~^/review/(custom|grid)_widget/[0-9]+\.(.+)$~', $path, $match ) ) {
70 + return '';
71 + }
72 +
73 + foreach ( explode( '/', $match[2] ) as $title_segment ) {
74 + if ( '.' === $title_segment || '..' === $title_segment ) {
75 + return '';
76 + }
77 + }
78 +
79 + $allowed_query_args = 'grid' === $match[1]
80 + ? array( 'cover_size', 'num_books', 'order', 'shelf', 'sort', 'widget_id' )
81 + : array( 'num_books', 'order', 'shelf', 'show_author', 'show_cover', 'show_rating', 'show_review', 'show_tags', 'show_title', 'sort', 'widget_id' );
82 + $query_args = array();
83 +
84 + wp_parse_str( $parsed['query'], $query_args );
85 +
86 + if ( array_diff( array_keys( $query_args ), $allowed_query_args ) ) {
87 + return '';
88 + }
89 +
90 + foreach ( $query_args as $value ) {
91 + if ( is_array( $value ) ) {
92 + return '';
93 + }
94 + }
95 +
96 + return 'https://www.goodreads.com' . $parsed['path'] . '?' . $parsed['query'];
97 +}
98 +
99 +/**
100 + * Dynamic rendering of the block.
101 + *
102 + * @param array $attr Array containing the Goodreads block attributes.
103 + *
104 + * @return string
105 + */
106 +function render_implementation( $attr ) {
107 + Jetpack_Gutenberg::load_assets_as_required( __DIR__ );
108 +
109 + if ( isset( $attr['id'] ) ) {
110 + if ( isset( $attr['link'] ) ) {
111 + $script_url = get_validated_script_url( $attr['link'] );
112 +
113 + if ( '' !== $script_url ) {
114 + wp_enqueue_script(
115 + 'jetpack-goodreads-' . esc_attr( $attr['id'] ),
116 + $script_url,
117 + array(),
118 + JETPACK__VERSION,
119 + true
120 + );
121 + }
122 + }
123 +
124 + $id = esc_attr( $attr['id'] );
125 + } else {
126 + $id = '';
127 + }
128 +
129 + $classes = esc_attr( Blocks::classes( Blocks::get_block_feature( __DIR__ ), $attr ) );
130 +
131 + return sprintf(
132 + '<div id="%1$s" class="%2$s"></div>',
133 + $id,
134 + $classes
135 + );
136 +}