68 * |
68 * |
69 * @since 4.7.0 |
69 * @since 4.7.0 |
70 * |
70 * |
71 * @param int $object_id Object ID to fetch meta for. |
71 * @param int $object_id Object ID to fetch meta for. |
72 * @param WP_REST_Request $request Full details about the request. |
72 * @param WP_REST_Request $request Full details about the request. |
73 * @return WP_Error|object Object containing the meta values by name, otherwise WP_Error object. |
73 * @return array Array containing the meta values keyed by name. |
74 */ |
74 */ |
75 public function get_value( $object_id, $request ) { |
75 public function get_value( $object_id, $request ) { |
76 $fields = $this->get_registered_fields(); |
76 $fields = $this->get_registered_fields(); |
77 $response = array(); |
77 $response = array(); |
78 |
78 |
79 foreach ( $fields as $meta_key => $args ) { |
79 foreach ( $fields as $meta_key => $args ) { |
80 $name = $args['name']; |
80 $name = $args['name']; |
81 $all_values = get_metadata( $this->get_meta_type(), $object_id, $meta_key, false ); |
81 $all_values = get_metadata( $this->get_meta_type(), $object_id, $meta_key, false ); |
|
82 |
82 if ( $args['single'] ) { |
83 if ( $args['single'] ) { |
83 if ( empty( $all_values ) ) { |
84 if ( empty( $all_values ) ) { |
84 $value = $args['schema']['default']; |
85 $value = $args['schema']['default']; |
85 } else { |
86 } else { |
86 $value = $all_values[0]; |
87 $value = $all_values[0]; |
87 } |
88 } |
|
89 |
88 $value = $this->prepare_value_for_response( $value, $request, $args ); |
90 $value = $this->prepare_value_for_response( $value, $request, $args ); |
89 } else { |
91 } else { |
90 $value = array(); |
92 $value = array(); |
|
93 |
91 foreach ( $all_values as $row ) { |
94 foreach ( $all_values as $row ) { |
92 $value[] = $this->prepare_value_for_response( $row, $request, $args ); |
95 $value[] = $this->prepare_value_for_response( $row, $request, $args ); |
93 } |
96 } |
94 } |
97 } |
95 |
98 |
124 /** |
127 /** |
125 * Updates meta values. |
128 * Updates meta values. |
126 * |
129 * |
127 * @since 4.7.0 |
130 * @since 4.7.0 |
128 * |
131 * |
129 * @param array $meta Array of meta parsed from the request. |
132 * @param array $meta Array of meta parsed from the request. |
130 * @param int $object_id Object ID to fetch meta for. |
133 * @param int $object_id Object ID to fetch meta for. |
131 * @return WP_Error|null WP_Error if one occurs, null on success. |
134 * @return null|WP_Error Null on success, WP_Error object on failure. |
132 */ |
135 */ |
133 public function update_value( $meta, $object_id ) { |
136 public function update_value( $meta, $object_id ) { |
134 $fields = $this->get_registered_fields(); |
137 $fields = $this->get_registered_fields(); |
|
138 |
135 foreach ( $fields as $meta_key => $args ) { |
139 foreach ( $fields as $meta_key => $args ) { |
136 $name = $args['name']; |
140 $name = $args['name']; |
137 if ( ! array_key_exists( $name, $meta ) ) { |
141 if ( ! array_key_exists( $name, $meta ) ) { |
138 continue; |
142 continue; |
139 } |
143 } |
141 /* |
145 /* |
142 * A null value means reset the field, which is essentially deleting it |
146 * A null value means reset the field, which is essentially deleting it |
143 * from the database and then relying on the default value. |
147 * from the database and then relying on the default value. |
144 */ |
148 */ |
145 if ( is_null( $meta[ $name ] ) ) { |
149 if ( is_null( $meta[ $name ] ) ) { |
|
150 $args = $this->get_registered_fields()[ $meta_key ]; |
|
151 |
|
152 if ( $args['single'] ) { |
|
153 $current = get_metadata( $this->get_meta_type(), $object_id, $meta_key, true ); |
|
154 |
|
155 if ( is_wp_error( rest_validate_value_from_schema( $current, $args['schema'] ) ) ) { |
|
156 return new WP_Error( |
|
157 'rest_invalid_stored_value', |
|
158 /* translators: %s: Custom field key. */ |
|
159 sprintf( __( 'The %s property has an invalid stored value, and cannot be updated to null.' ), $name ), |
|
160 array( 'status' => 500 ) |
|
161 ); |
|
162 } |
|
163 } |
|
164 |
146 $result = $this->delete_meta_value( $object_id, $meta_key, $name ); |
165 $result = $this->delete_meta_value( $object_id, $meta_key, $name ); |
147 if ( is_wp_error( $result ) ) { |
166 if ( is_wp_error( $result ) ) { |
148 return $result; |
167 return $result; |
149 } |
168 } |
150 continue; |
169 continue; |
151 } |
170 } |
152 |
171 |
153 $is_valid = rest_validate_value_from_schema( $meta[ $name ], $args['schema'], 'meta.' . $name ); |
172 $value = $meta[ $name ]; |
|
173 |
|
174 if ( ! $args['single'] && is_array( $value ) && count( array_filter( $value, 'is_null' ) ) ) { |
|
175 return new WP_Error( |
|
176 'rest_invalid_stored_value', |
|
177 /* translators: %s: Custom field key. */ |
|
178 sprintf( __( 'The %s property has an invalid stored value, and cannot be updated to null.' ), $name ), |
|
179 array( 'status' => 500 ) |
|
180 ); |
|
181 } |
|
182 |
|
183 $is_valid = rest_validate_value_from_schema( $value, $args['schema'], 'meta.' . $name ); |
154 if ( is_wp_error( $is_valid ) ) { |
184 if ( is_wp_error( $is_valid ) ) { |
155 $is_valid->add_data( array( 'status' => 400 ) ); |
185 $is_valid->add_data( array( 'status' => 400 ) ); |
156 return $is_valid; |
186 return $is_valid; |
157 } |
187 } |
158 |
188 |
159 $value = rest_sanitize_value_from_schema( $meta[ $name ], $args['schema'] ); |
189 $value = rest_sanitize_value_from_schema( $value, $args['schema'] ); |
160 |
190 |
161 if ( $args['single'] ) { |
191 if ( $args['single'] ) { |
162 $result = $this->update_meta_value( $object_id, $meta_key, $name, $value ); |
192 $result = $this->update_meta_value( $object_id, $meta_key, $name, $value ); |
163 } else { |
193 } else { |
164 $result = $this->update_multi_meta_value( $object_id, $meta_key, $name, $value ); |
194 $result = $this->update_multi_meta_value( $object_id, $meta_key, $name, $value ); |
182 * @param string $name Name for the field that is exposed in the REST API. |
212 * @param string $name Name for the field that is exposed in the REST API. |
183 * @return bool|WP_Error True if meta field is deleted, WP_Error otherwise. |
213 * @return bool|WP_Error True if meta field is deleted, WP_Error otherwise. |
184 */ |
214 */ |
185 protected function delete_meta_value( $object_id, $meta_key, $name ) { |
215 protected function delete_meta_value( $object_id, $meta_key, $name ) { |
186 $meta_type = $this->get_meta_type(); |
216 $meta_type = $this->get_meta_type(); |
|
217 |
187 if ( ! current_user_can( "delete_{$meta_type}_meta", $object_id, $meta_key ) ) { |
218 if ( ! current_user_can( "delete_{$meta_type}_meta", $object_id, $meta_key ) ) { |
188 return new WP_Error( |
219 return new WP_Error( |
189 'rest_cannot_delete', |
220 'rest_cannot_delete', |
190 /* translators: %s: custom field key */ |
221 /* translators: %s: Custom field key. */ |
191 sprintf( __( 'Sorry, you are not allowed to edit the %s custom field.' ), $name ), |
222 sprintf( __( 'Sorry, you are not allowed to edit the %s custom field.' ), $name ), |
192 array( |
223 array( |
193 'key' => $name, |
224 'key' => $name, |
194 'status' => rest_authorization_required_code(), |
225 'status' => rest_authorization_required_code(), |
195 ) |
226 ) |
223 * @param array $values List of values to update to. |
254 * @param array $values List of values to update to. |
224 * @return bool|WP_Error True if meta fields are updated, WP_Error otherwise. |
255 * @return bool|WP_Error True if meta fields are updated, WP_Error otherwise. |
225 */ |
256 */ |
226 protected function update_multi_meta_value( $object_id, $meta_key, $name, $values ) { |
257 protected function update_multi_meta_value( $object_id, $meta_key, $name, $values ) { |
227 $meta_type = $this->get_meta_type(); |
258 $meta_type = $this->get_meta_type(); |
|
259 |
228 if ( ! current_user_can( "edit_{$meta_type}_meta", $object_id, $meta_key ) ) { |
260 if ( ! current_user_can( "edit_{$meta_type}_meta", $object_id, $meta_key ) ) { |
229 return new WP_Error( |
261 return new WP_Error( |
230 'rest_cannot_update', |
262 'rest_cannot_update', |
231 /* translators: %s: custom field key */ |
263 /* translators: %s: Custom field key. */ |
232 sprintf( __( 'Sorry, you are not allowed to edit the %s custom field.' ), $name ), |
264 sprintf( __( 'Sorry, you are not allowed to edit the %s custom field.' ), $name ), |
233 array( |
265 array( |
234 'key' => $name, |
266 'key' => $name, |
235 'status' => rest_authorization_required_code(), |
267 'status' => rest_authorization_required_code(), |
236 ) |
268 ) |
237 ); |
269 ); |
238 } |
270 } |
239 |
271 |
240 $current = get_metadata( $meta_type, $object_id, $meta_key, false ); |
272 $current_values = get_metadata( $meta_type, $object_id, $meta_key, false ); |
241 |
273 $subtype = get_object_subtype( $meta_type, $object_id ); |
242 $to_remove = $current; |
274 |
|
275 $to_remove = $current_values; |
243 $to_add = $values; |
276 $to_add = $values; |
244 |
277 |
245 foreach ( $to_add as $add_key => $value ) { |
278 foreach ( $to_add as $add_key => $value ) { |
246 $remove_keys = array_keys( $to_remove, $value, true ); |
279 $remove_keys = array_keys( |
|
280 array_filter( |
|
281 $current_values, |
|
282 function ( $stored_value ) use ( $meta_key, $subtype, $value ) { |
|
283 return $this->is_meta_value_same_as_stored_value( $meta_key, $subtype, $stored_value, $value ); |
|
284 } |
|
285 ) |
|
286 ); |
247 |
287 |
248 if ( empty( $remove_keys ) ) { |
288 if ( empty( $remove_keys ) ) { |
249 continue; |
289 continue; |
250 } |
290 } |
251 |
291 |
258 |
298 |
259 unset( $to_remove[ $remove_key ] ); |
299 unset( $to_remove[ $remove_key ] ); |
260 unset( $to_add[ $add_key ] ); |
300 unset( $to_add[ $add_key ] ); |
261 } |
301 } |
262 |
302 |
263 // `delete_metadata` removes _all_ instances of the value, so only call once. |
303 /* |
264 $to_remove = array_unique( $to_remove ); |
304 * `delete_metadata` removes _all_ instances of the value, so only call once. Otherwise, |
|
305 * `delete_metadata` will return false for subsequent calls of the same value. |
|
306 * Use serialization to produce a predictable string that can be used by array_unique. |
|
307 */ |
|
308 $to_remove = array_map( 'maybe_unserialize', array_unique( array_map( 'maybe_serialize', $to_remove ) ) ); |
265 |
309 |
266 foreach ( $to_remove as $value ) { |
310 foreach ( $to_remove as $value ) { |
267 if ( ! delete_metadata( $meta_type, $object_id, wp_slash( $meta_key ), wp_slash( $value ) ) ) { |
311 if ( ! delete_metadata( $meta_type, $object_id, wp_slash( $meta_key ), wp_slash( $value ) ) ) { |
268 return new WP_Error( |
312 return new WP_Error( |
269 'rest_meta_database_error', |
313 'rest_meta_database_error', |
270 __( 'Could not update meta value in database.' ), |
314 /* translators: %s: Custom field key. */ |
|
315 sprintf( __( 'Could not update the meta value of %s in database.' ), $meta_key ), |
271 array( |
316 array( |
272 'key' => $name, |
317 'key' => $name, |
273 'status' => WP_Http::INTERNAL_SERVER_ERROR, |
318 'status' => WP_Http::INTERNAL_SERVER_ERROR, |
274 ) |
319 ) |
275 ); |
320 ); |
278 |
323 |
279 foreach ( $to_add as $value ) { |
324 foreach ( $to_add as $value ) { |
280 if ( ! add_metadata( $meta_type, $object_id, wp_slash( $meta_key ), wp_slash( $value ) ) ) { |
325 if ( ! add_metadata( $meta_type, $object_id, wp_slash( $meta_key ), wp_slash( $value ) ) ) { |
281 return new WP_Error( |
326 return new WP_Error( |
282 'rest_meta_database_error', |
327 'rest_meta_database_error', |
283 __( 'Could not update meta value in database.' ), |
328 /* translators: %s: Custom field key. */ |
|
329 sprintf( __( 'Could not update the meta value of %s in database.' ), $meta_key ), |
284 array( |
330 array( |
285 'key' => $name, |
331 'key' => $name, |
286 'status' => WP_Http::INTERNAL_SERVER_ERROR, |
332 'status' => WP_Http::INTERNAL_SERVER_ERROR, |
287 ) |
333 ) |
288 ); |
334 ); |
303 * @param mixed $value Updated value. |
349 * @param mixed $value Updated value. |
304 * @return bool|WP_Error True if the meta field was updated, WP_Error otherwise. |
350 * @return bool|WP_Error True if the meta field was updated, WP_Error otherwise. |
305 */ |
351 */ |
306 protected function update_meta_value( $object_id, $meta_key, $name, $value ) { |
352 protected function update_meta_value( $object_id, $meta_key, $name, $value ) { |
307 $meta_type = $this->get_meta_type(); |
353 $meta_type = $this->get_meta_type(); |
|
354 |
308 if ( ! current_user_can( "edit_{$meta_type}_meta", $object_id, $meta_key ) ) { |
355 if ( ! current_user_can( "edit_{$meta_type}_meta", $object_id, $meta_key ) ) { |
309 return new WP_Error( |
356 return new WP_Error( |
310 'rest_cannot_update', |
357 'rest_cannot_update', |
311 /* translators: %s: custom field key */ |
358 /* translators: %s: Custom field key. */ |
312 sprintf( __( 'Sorry, you are not allowed to edit the %s custom field.' ), $name ), |
359 sprintf( __( 'Sorry, you are not allowed to edit the %s custom field.' ), $name ), |
313 array( |
360 array( |
314 'key' => $name, |
361 'key' => $name, |
315 'status' => rest_authorization_required_code(), |
362 'status' => rest_authorization_required_code(), |
316 ) |
363 ) |
319 |
366 |
320 // Do the exact same check for a duplicate value as in update_metadata() to avoid update_metadata() returning false. |
367 // Do the exact same check for a duplicate value as in update_metadata() to avoid update_metadata() returning false. |
321 $old_value = get_metadata( $meta_type, $object_id, $meta_key ); |
368 $old_value = get_metadata( $meta_type, $object_id, $meta_key ); |
322 $subtype = get_object_subtype( $meta_type, $object_id ); |
369 $subtype = get_object_subtype( $meta_type, $object_id ); |
323 |
370 |
324 if ( 1 === count( $old_value ) ) { |
371 if ( 1 === count( $old_value ) && $this->is_meta_value_same_as_stored_value( $meta_key, $subtype, $old_value[0], $value ) ) { |
325 if ( (string) sanitize_meta( $meta_key, $value, $meta_type, $subtype ) === $old_value[0] ) { |
372 return true; |
326 return true; |
373 } |
327 } |
374 |
328 } |
375 if ( ! update_metadata( $meta_type, $object_id, wp_slash( $meta_key ), wp_slash_strings_only( $value ) ) ) { |
329 |
|
330 if ( ! update_metadata( $meta_type, $object_id, wp_slash( $meta_key ), wp_slash( $value ) ) ) { |
|
331 return new WP_Error( |
376 return new WP_Error( |
332 'rest_meta_database_error', |
377 'rest_meta_database_error', |
333 __( 'Could not update meta value in database.' ), |
378 /* translators: %s: Custom field key. */ |
|
379 sprintf( __( 'Could not update the meta value of %s in database.' ), $meta_key ), |
334 array( |
380 array( |
335 'key' => $name, |
381 'key' => $name, |
336 'status' => WP_Http::INTERNAL_SERVER_ERROR, |
382 'status' => WP_Http::INTERNAL_SERVER_ERROR, |
337 ) |
383 ) |
338 ); |
384 ); |
339 } |
385 } |
340 |
386 |
341 return true; |
387 return true; |
|
388 } |
|
389 |
|
390 /** |
|
391 * Checks if the user provided value is equivalent to a stored value for the given meta key. |
|
392 * |
|
393 * @since 5.5.0 |
|
394 * |
|
395 * @param string $meta_key The meta key being checked. |
|
396 * @param string $subtype The object subtype. |
|
397 * @param mixed $stored_value The currently stored value retrieved from get_metadata(). |
|
398 * @param mixed $user_value The value provided by the user. |
|
399 * @return bool |
|
400 */ |
|
401 protected function is_meta_value_same_as_stored_value( $meta_key, $subtype, $stored_value, $user_value ) { |
|
402 $args = $this->get_registered_fields()[ $meta_key ]; |
|
403 $sanitized = sanitize_meta( $meta_key, $user_value, $this->get_meta_type(), $subtype ); |
|
404 |
|
405 if ( in_array( $args['type'], array( 'string', 'number', 'integer', 'boolean' ), true ) ) { |
|
406 // The return value of get_metadata will always be a string for scalar types. |
|
407 $sanitized = (string) $sanitized; |
|
408 } |
|
409 |
|
410 return $sanitized === $stored_value; |
342 } |
411 } |
343 |
412 |
344 /** |
413 /** |
345 * Retrieves all the registered meta fields. |
414 * Retrieves all the registered meta fields. |
346 * |
415 * |
388 $rest_args['schema'] = array_merge( $default_schema, $rest_args['schema'] ); |
457 $rest_args['schema'] = array_merge( $default_schema, $rest_args['schema'] ); |
389 |
458 |
390 $type = ! empty( $rest_args['type'] ) ? $rest_args['type'] : null; |
459 $type = ! empty( $rest_args['type'] ) ? $rest_args['type'] : null; |
391 $type = ! empty( $rest_args['schema']['type'] ) ? $rest_args['schema']['type'] : $type; |
460 $type = ! empty( $rest_args['schema']['type'] ) ? $rest_args['schema']['type'] : $type; |
392 |
461 |
393 if ( ! in_array( $type, array( 'string', 'boolean', 'integer', 'number' ) ) ) { |
462 if ( null === $rest_args['schema']['default'] ) { |
|
463 $rest_args['schema']['default'] = static::get_empty_value_for_type( $type ); |
|
464 } |
|
465 |
|
466 $rest_args['schema'] = $this->default_additional_properties_to_false( $rest_args['schema'] ); |
|
467 |
|
468 if ( ! in_array( $type, array( 'string', 'boolean', 'integer', 'number', 'array', 'object' ), true ) ) { |
394 continue; |
469 continue; |
395 } |
470 } |
396 |
471 |
397 if ( empty( $rest_args['single'] ) ) { |
472 if ( empty( $rest_args['single'] ) ) { |
398 $rest_args['schema']['items'] = array( |
473 $rest_args['schema'] = array( |
399 'type' => $rest_args['type'], |
474 'type' => 'array', |
|
475 'items' => $rest_args['schema'], |
400 ); |
476 ); |
401 $rest_args['schema']['type'] = 'array'; |
|
402 } |
477 } |
403 |
478 |
404 $registered[ $name ] = $rest_args; |
479 $registered[ $name ] = $rest_args; |
405 } |
480 } |
406 |
481 |
447 * @param WP_REST_Request $request Request object. |
522 * @param WP_REST_Request $request Request object. |
448 * @param array $args REST-specific options for the meta key. |
523 * @param array $args REST-specific options for the meta key. |
449 * @return mixed Value prepared for output. If a non-JsonSerializable object, null. |
524 * @return mixed Value prepared for output. If a non-JsonSerializable object, null. |
450 */ |
525 */ |
451 public static function prepare_value( $value, $request, $args ) { |
526 public static function prepare_value( $value, $request, $args ) { |
452 $type = $args['schema']['type']; |
527 if ( $args['single'] ) { |
453 |
528 $schema = $args['schema']; |
454 // For multi-value fields, check the item type instead. |
529 } else { |
455 if ( 'array' === $type && ! empty( $args['schema']['items']['type'] ) ) { |
530 $schema = $args['schema']['items']; |
456 $type = $args['schema']['items']['type']; |
531 } |
457 } |
532 |
458 |
533 if ( '' === $value && in_array( $schema['type'], array( 'boolean', 'integer', 'number' ), true ) ) { |
459 switch ( $type ) { |
534 $value = static::get_empty_value_for_type( $schema['type'] ); |
460 case 'string': |
535 } |
461 $value = (string) $value; |
536 |
462 break; |
537 if ( is_wp_error( rest_validate_value_from_schema( $value, $schema ) ) ) { |
463 case 'integer': |
|
464 $value = (int) $value; |
|
465 break; |
|
466 case 'number': |
|
467 $value = (float) $value; |
|
468 break; |
|
469 case 'boolean': |
|
470 $value = (bool) $value; |
|
471 break; |
|
472 } |
|
473 |
|
474 // Don't allow objects to be output. |
|
475 if ( is_object( $value ) && ! ( $value instanceof JsonSerializable ) ) { |
|
476 return null; |
538 return null; |
477 } |
539 } |
478 |
540 |
479 return $value; |
541 return rest_sanitize_value_from_schema( $value, $schema ); |
480 } |
542 } |
481 |
543 |
482 /** |
544 /** |
483 * Check the 'meta' value of a request is an associative array. |
545 * Check the 'meta' value of a request is an associative array. |
484 * |
546 * |
485 * @since 4.7.0 |
547 * @since 4.7.0 |
486 * |
548 * |
487 * @param mixed $value The meta value submitted in the request. |
549 * @param mixed $value The meta value submitted in the request. |
488 * @param WP_REST_Request $request Full details about the request. |
550 * @param WP_REST_Request $request Full details about the request. |
489 * @param string $param The parameter name. |
551 * @param string $param The parameter name. |
490 * @return WP_Error|string The meta array, if valid, otherwise an error. |
552 * @return array|false The meta array, if valid, false otherwise. |
491 */ |
553 */ |
492 public function check_meta_is_array( $value, $request, $param ) { |
554 public function check_meta_is_array( $value, $request, $param ) { |
493 if ( ! is_array( $value ) ) { |
555 if ( ! is_array( $value ) ) { |
494 return false; |
556 return false; |
495 } |
557 } |
496 |
558 |
497 return $value; |
559 return $value; |
498 } |
560 } |
|
561 |
|
562 /** |
|
563 * Recursively add additionalProperties = false to all objects in a schema if no additionalProperties setting |
|
564 * is specified. |
|
565 * |
|
566 * This is needed to restrict properties of objects in meta values to only |
|
567 * registered items, as the REST API will allow additional properties by |
|
568 * default. |
|
569 * |
|
570 * @since 5.3.0 |
|
571 * |
|
572 * @param array $schema The schema array. |
|
573 * @return array |
|
574 */ |
|
575 protected function default_additional_properties_to_false( $schema ) { |
|
576 switch ( $schema['type'] ) { |
|
577 case 'object': |
|
578 foreach ( $schema['properties'] as $key => $child_schema ) { |
|
579 $schema['properties'][ $key ] = $this->default_additional_properties_to_false( $child_schema ); |
|
580 } |
|
581 |
|
582 if ( ! isset( $schema['additionalProperties'] ) ) { |
|
583 $schema['additionalProperties'] = false; |
|
584 } |
|
585 break; |
|
586 case 'array': |
|
587 $schema['items'] = $this->default_additional_properties_to_false( $schema['items'] ); |
|
588 break; |
|
589 } |
|
590 |
|
591 return $schema; |
|
592 } |
|
593 |
|
594 /** |
|
595 * Gets the empty value for a schema type. |
|
596 * |
|
597 * @since 5.3.0 |
|
598 * |
|
599 * @param string $type The schema type. |
|
600 * @return mixed |
|
601 */ |
|
602 protected static function get_empty_value_for_type( $type ) { |
|
603 switch ( $type ) { |
|
604 case 'string': |
|
605 return ''; |
|
606 case 'boolean': |
|
607 return false; |
|
608 case 'integer': |
|
609 return 0; |
|
610 case 'number': |
|
611 return 0.0; |
|
612 case 'array': |
|
613 case 'object': |
|
614 return array(); |
|
615 default: |
|
616 return null; |
|
617 } |
|
618 } |
499 } |
619 } |