PluginProbe
Blocks – Reusable Content, Shortcodes & Site Variables / trunk
Blocks – Reusable Content, Shortcodes & Site Variables vtrunk
26.09.03.15 26.08.31.21 26.08.22.20 26.08.22.22 26.08.22.17 26.08.22.13 26.08.21.19 26.08.07.23 26.07.19.14 26.07.13.21 26.07.13.17 26.07.12.13 026.07.07.21 026.07.05.18 026.06.26.20 026.06.26.21 026.06.08.20 026.05.13.14 026.04.29.10 trunk 026.02.22.22 026.03.16.23 026.04.23.13
blocks / src / Transfer / TransferValidator.php

TransferValidator.php in Blocks – Reusable Content, Shortcodes & Site Variables trunk, at src/Transfer/TransferValidator.php

197 lines 4.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 declare(strict_types=1);
4
5 namespace RenzoJohnson\Blocks\Transfer;
6
7 \defined( 'ABSPATH' ) || exit;
8
9 final class TransferValidator {
10
11 public const FORMAT = 1;
12
13 public function __construct( private readonly PropertySanitizer $property_sanitizer ) {
14 }
15
16 /**
17 * @param array<mixed, mixed> $payload
18 * @return array{
19 * format: int,
20 * plugin: string,
21 * blocks: list<array{
22 * title: string,
23 * status: string,
24 * locale: string,
25 * properties: array<mixed, mixed>,
26 * categories: list<string>,
27 * tags: list<string>
28 * }>,
29 * variables: array<mixed, mixed>
30 * }|\WP_Error
31 */
32 public function validate( array $payload ): array|\WP_Error {
33 if (
34 self::FORMAT !== ( $payload['format'] ?? null )
35 || 'blocks' !== ( $payload['plugin'] ?? null )
36 || ! isset( $payload['blocks'] )
37 || ! \is_array( $payload['blocks'] )
38 ) {
39 return $this->invalid_file();
40 }
41
42 $blocks = array();
43
44 foreach ( $payload['blocks'] as $index => $item ) {
45 $block = $this->validate_block( $item, $index );
46
47 if ( \is_wp_error( $block ) ) {
48 return $block;
49 }
50
51 $blocks[] = $block;
52 }
53
54 $variables = $payload['variables'] ?? array();
55
56 if ( ! \is_array( $variables ) ) {
57 return $this->invalid_file();
58 }
59
60 return array(
61 'format' => self::FORMAT,
62 'plugin' => 'blocks',
63 'blocks' => $blocks,
64 'variables' => $variables,
65 );
66 }
67
68 /**
69 * @return array{
70 * title: string,
71 * status: string,
72 * locale: string,
73 * properties: array<mixed, mixed>,
74 * categories: list<string>,
75 * tags: list<string>
76 * }|\WP_Error
77 */
78 private function validate_block( mixed $item, int|string $index ): array|\WP_Error {
79 if ( ! \is_array( $item ) || ! isset( $item['title'] ) || ! \is_string( $item['title'] ) ) {
80 return $this->invalid_block( $index );
81 }
82
83 $title = \sanitize_text_field( $item['title'] );
84
85 if ( '' === \trim( $title ) ) {
86 return $this->invalid_block( $index );
87 }
88
89 $properties = $item['properties'] ?? array();
90
91 if ( ! \is_array( $properties ) || ! $this->is_json_value( $properties ) ) {
92 return $this->invalid_block( $index );
93 }
94
95 $properties = $this->property_sanitizer->sanitize( $properties );
96
97 $categories = $this->validate_terms( $item['categories'] ?? array(), $index );
98
99 if ( \is_wp_error( $categories ) ) {
100 return $categories;
101 }
102
103 $tags = $this->validate_terms( $item['tags'] ?? array(), $index );
104
105 if ( \is_wp_error( $tags ) ) {
106 return $tags;
107 }
108
109 $locale = '';
110
111 if ( isset( $item['locale'] ) ) {
112 if ( ! \is_string( $item['locale'] ) || ( '' !== $item['locale'] && ! \blocks_is_valid_locale( $item['locale'] ) ) ) {
113 return $this->invalid_block( $index );
114 }
115
116 $locale = $item['locale'];
117 }
118
119 $status = $item['status'] ?? 'draft';
120
121 if ( ! \is_string( $status ) || ! \in_array( $status, array( 'publish', 'draft', 'private' ), true ) ) {
122 $status = 'draft';
123 }
124
125 return array(
126 'title' => $title,
127 'status' => $status,
128 'locale' => $locale,
129 'properties' => $properties,
130 'categories' => $categories,
131 'tags' => $tags,
132 );
133 }
134
135 /**
136 * @return list<string>|\WP_Error
137 */
138 private function validate_terms( mixed $terms, int|string $index ): array|\WP_Error {
139 if ( ! \is_array( $terms ) ) {
140 return $this->invalid_block( $index );
141 }
142
143 $normalized = array();
144
145 foreach ( $terms as $term ) {
146 if ( ! \is_string( $term ) ) {
147 return $this->invalid_block( $index );
148 }
149
150 $name = \sanitize_text_field( $term );
151
152 if ( '' !== $name ) {
153 $normalized[] = $name;
154 }
155 }
156
157 return \array_values( \array_unique( $normalized ) );
158 }
159
160 private function is_json_value( mixed $value ): bool {
161 if ( null === $value || \is_scalar( $value ) ) {
162 return true;
163 }
164
165 if ( ! \is_array( $value ) ) {
166 return false;
167 }
168
169 foreach ( $value as $key => $nested ) {
170 if ( ! \is_int( $key ) && ! \is_string( $key ) ) {
171 return false;
172 }
173
174 if ( ! $this->is_json_value( $nested ) ) {
175 return false;
176 }
177 }
178
179 return true;
180 }
181
182 private function invalid_file(): \WP_Error {
183 return new \WP_Error( 'blocks_import_invalid', \__( 'Not a valid Blocks export file.', 'blocks' ) );
184 }
185
186 private function invalid_block( int|string $index ): \WP_Error {
187 return new \WP_Error(
188 'blocks_import_invalid_block',
189 \sprintf(
190 /* translators: %s: zero-based block index. */
191 \__( 'Block at index %s is malformed.', 'blocks' ),
192 (string) $index,
193 ),
194 );
195 }
196 }
197