PluginProbe
WP Data Access – App Builder for Tables, Forms, Charts, Maps & Dashboards / 5.5.41
WP Data Access – App Builder for Tables, Forms, Charts, Maps & Dashboards v5.5.41
5.5.83 5.5.82 5.5.81 5.5.80 5.5.79 5.5.77 5.5.76 5.5.75 5.5.73 5.5.72 5.5.22 5.5.23 5.5.29 5.5.3 5.5.31 5.5.32 5.5.34 5.5.35 5.5.36 5.5.37 5.5.4 5.5.40 5.5.41 5.5.42 5.5.43 All 159 releases
wp-data-access / WPDataAccess / API / WPDA_Actions.php

WPDA_Actions.php in WP Data Access – App Builder for Tables, Forms, Charts, Maps & Dashboards 5.5.41, at WPDataAccess/API/WPDA_Actions.php

557 lines 25.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace WPDataAccess\API;
4
5 use WPDataAccess\Connection\WPDADB;
6 use WPDataAccess\Plugin_Table_Models\WPDA_Media_Model;
7 use WPDataAccess\Plugin_Table_Models\WPDA_Table_Settings_Model;
8 use WPDataAccess\Plugin_Table_Models\WPDA_User_Menus_Model;
9 use WPDataAccess\Utilities\WPDA_Mail;
10 use WPDataAccess\WPDA;
11 class WPDA_Actions extends WPDA_API_Core {
12 protected $file_pointer;
13
14 protected $file_content;
15
16 public function register_rest_routes() {
17 register_rest_route( WPDA_API::WPDA_NAMESPACE, 'action/rename', array(
18 'methods' => array('POST'),
19 'callback' => array($this, 'action_rename'),
20 'permission_callback' => '__return_true',
21 'args' => array(
22 'dbs' => $this->get_param( 'dbs', __( 'Local database name or remote connection string (does not accept system schemas)', 'wp-data-access' ) ),
23 'from_tbl' => $this->get_param( 'tbl', __( 'Source table name (does not rename WordPress tables)', 'wp-data-access' ) ),
24 'to_tbl' => $this->get_param( 'tbl', __( 'Destination table name (cannot overwrite existing table)', 'wp-data-access' ) ),
25 ),
26 ) );
27 register_rest_route( WPDA_API::WPDA_NAMESPACE, 'action/copy', array(
28 'methods' => array('POST'),
29 'callback' => array($this, 'action_copy'),
30 'permission_callback' => '__return_true',
31 'args' => array(
32 'from_dbs' => $this->get_param( 'dbs', __( 'Source database name or remote connection string', 'wp-data-access' ) ),
33 'to_dbs' => $this->get_param( 'dbs', __( 'Destination database name or remote connection string', 'wp-data-access' ) ),
34 'from_tbl' => $this->get_param( 'tbl', __( 'Source table name', 'wp-data-access' ) ),
35 'to_tbl' => $this->get_param( 'tbl', __( 'Destination table name', 'wp-data-access' ) ),
36 'copy_data' => array(
37 'required' => true,
38 'type' => 'boolean',
39 'description' => __( 'Copy data from source to destination table', 'wp-data-access' ),
40 'sanitize_callback' => 'sanitize_text_field',
41 'validate_callback' => 'rest_validate_request_arg',
42 ),
43 ),
44 ) );
45 register_rest_route( WPDA_API::WPDA_NAMESPACE, 'action/truncate', array(
46 'methods' => array('POST'),
47 'callback' => array($this, 'action_truncate'),
48 'permission_callback' => '__return_true',
49 'args' => array(
50 'dbs' => $this->get_param( 'dbs', __( 'Local database name or remote connection string (does not accept system schemas)', 'wp-data-access' ) ),
51 'tbl' => $this->get_param( 'tbl', __( 'Source table name (does not truncate WordPress tables)', 'wp-data-access' ) ),
52 ),
53 ) );
54 register_rest_route( WPDA_API::WPDA_NAMESPACE, 'action/drop', array(
55 'methods' => array('POST'),
56 'callback' => array($this, 'action_drop'),
57 'permission_callback' => '__return_true',
58 'args' => array(
59 'dbs' => $this->get_param( 'dbs', __( 'Local database name or remote connection string (does not accept system schemas)', 'wp-data-access' ) ),
60 'tbl' => $this->get_param( 'tbl', __( 'Source table name (does not drop WordPress tables)', 'wp-data-access' ) ),
61 'typ' => $this->get_param( 'typ' ),
62 ),
63 ) );
64 register_rest_route( WPDA_API::WPDA_NAMESPACE, 'action/import', array(
65 'methods' => array('POST'),
66 'callback' => array($this, 'action_import'),
67 'permission_callback' => '__return_true',
68 ) );
69 register_rest_route( WPDA_API::WPDA_NAMESPACE, 'action/mail', array(
70 'methods' => array('POST'),
71 'callback' => array($this, 'action_mail'),
72 'permission_callback' => '__return_true',
73 'args' => array(
74 'to' => array(
75 'required' => false,
76 'type' => 'string',
77 'description' => __( 'To', 'wp-data-access' ),
78 'sanitize_callback' => 'sanitize_text_field',
79 'validate_callback' => 'rest_validate_request_arg',
80 ),
81 'subject' => array(
82 'required' => false,
83 'type' => 'string',
84 'description' => __( 'Subject', 'wp-data-access' ),
85 'sanitize_callback' => 'sanitize_text_field',
86 'validate_callback' => 'rest_validate_request_arg',
87 ),
88 'message' => array(
89 'required' => false,
90 'type' => 'string',
91 'description' => __( 'Message', 'wp-data-access' ),
92 'sanitize_callback' => 'sanitize_text_field',
93 'validate_callback' => 'rest_validate_request_arg',
94 ),
95 ),
96 ) );
97 }
98
99 public function action_mail( $request ) {
100 if ( !$this->current_user_can_access() ) {
101 return $this->unauthorized();
102 }
103 if ( !$this->current_user_token_valid( $request ) ) {
104 return $this->invalid_nonce();
105 }
106 $to = $request['to'];
107 $subject = $request['subject'];
108 $message = $request['message'];
109 return WPDA_Mail::send( $to, $subject, $message );
110 }
111
112 public function action_import( $request ) {
113 if ( !$this->current_user_can_access() ) {
114 return $this->unauthorized();
115 }
116 if ( !$this->current_user_token_valid( $request ) ) {
117 return $this->invalid_nonce();
118 }
119 $dbs = $this->sanitize_db_identifier( $request->get_param( 'dbs' ) );
120 $files = $request->get_file_params();
121 $response = array();
122 $errors = false;
123 if ( 0 === count( $files ) || '' === trim( $dbs ) ) {
124 return $this->bad_request();
125 } else {
126 foreach ( $files as $file ) {
127 // phpcs:disable
128 $temp_file_name = sanitize_text_field( $file['tmp_name'] );
129 // For Windows: do NOT unslash!
130 // phpcs:enable
131 $temp_file_type = sanitize_text_field( wp_unslash( $file['type'] ) );
132 $orig_file_name = sanitize_text_field( wp_unslash( $file['name'] ) );
133 if ( 0 === $file['error'] && is_uploaded_file( $temp_file_name ) ) {
134 if ( 'application/zip' === $temp_file_type || 'application/x-zip' === $temp_file_type || 'application/x-zip-compressed' === $temp_file_type ) {
135 // Process ZIP file.
136 if ( class_exists( '\\ZipArchive' ) ) {
137 $zip = new \ZipArchive();
138 if ( $zip->open( $temp_file_name ) ) {
139 for ($i = 0; $i < $zip->numFiles; $i++) {
140 $this->file_pointer = $zip->getStream( $zip->getNameIndex( $i ) );
141 $status = $this->import( $zip->getNameIndex( $i ), $dbs );
142 if ( isset( $status['status'], $status['msg'] ) ) {
143 $errors = $errors || 'error' === $status['status'];
144 $response[] = array(
145 $zip->getNameIndex( $i ) => array(
146 'status' => $status['status'],
147 'msg' => $status['msg'],
148 'errors' => $status['errors'],
149 ),
150 );
151 }
152 }
153 } else {
154 // Error reading ZIP file.
155 $errors = true;
156 $response[] = array(
157 $orig_file_name => array(
158 'status' => 'error',
159 'msg' => sprintf( __( 'Import failed [error reading ZIP file `%s`]', 'wp-data-access' ), $orig_file_name ),
160 ),
161 );
162 }
163 } else {
164 // ZipArchive not installed.
165 $errors = true;
166 $response[] = array(
167 $orig_file_name => array(
168 'status' => 'error',
169 'msg' => sprintf( __( 'Import failed - ZipArchive not installed %s', 'wp-data-access' ) ),
170 ),
171 );
172 }
173 } else {
174 // Process plain file.
175 $this->file_pointer = fopen( $temp_file_name, 'rb' );
176 $status = $this->import( $orig_file_name, $dbs );
177 if ( isset( $status['status'], $status['msg'] ) ) {
178 $errors = $errors || 'error' === $status['status'];
179 $response[] = array(
180 $orig_file_name => array(
181 'status' => $status['status'],
182 'msg' => $status['msg'],
183 'errors' => $status['errors'],
184 ),
185 );
186 }
187 }
188 }
189 }
190 }
191 if ( $errors ) {
192 $msg = __( 'File(s) imported with errors', 'wp-data-access' );
193 } else {
194 $msg = __( 'File(s) successfully imported', 'wp-data-access' );
195 }
196 return $this->WPDA_Rest_Response( $msg, null, array(
197 'imported' => $response,
198 ) );
199 }
200
201 public function action_drop( $request ) {
202 if ( !$this->current_user_can_access() ) {
203 return $this->unauthorized();
204 }
205 if ( !$this->current_user_token_valid( $request ) ) {
206 return $this->invalid_nonce();
207 }
208 $dbs = $request->get_param( 'dbs' );
209 $tbl = $request->get_param( 'tbl' );
210 $typ = $request->get_param( 'typ' );
211 if ( '' === $dbs || '' === $tbl ) {
212 return $this->bad_request();
213 }
214 global $wpdb;
215 if ( $wpdb->dbname === $dbs && in_array( $tbl, $wpdb->tables() ) ) {
216 return $this->unauthorized();
217 }
218 $msg = $this->drop( $dbs, $tbl, $typ );
219 if ( '' === $msg ) {
220 if ( 1 === $typ ) {
221 return $this->WPDA_Rest_Response( __( 'View successfully dropped', 'wp-data-access' ) );
222 } else {
223 return $this->WPDA_Rest_Response( __( 'Table successfully dropped', 'wp-data-access' ) );
224 }
225 } else {
226 return new \WP_Error('error', $msg, array(
227 'status' => 403,
228 ));
229 }
230 }
231
232 public function action_truncate( $request ) {
233 if ( !$this->current_user_can_access() ) {
234 return $this->unauthorized();
235 }
236 if ( !$this->current_user_token_valid( $request ) ) {
237 return $this->invalid_nonce();
238 }
239 $dbs = $request->get_param( 'dbs' );
240 $tbl = $request->get_param( 'tbl' );
241 if ( '' === $dbs || '' === $tbl ) {
242 return $this->bad_request();
243 }
244 global $wpdb;
245 if ( $wpdb->dbname === $dbs && in_array( $tbl, $wpdb->tables() ) ) {
246 return $this->unauthorized();
247 }
248 $msg = $this->truncate( $dbs, $tbl );
249 if ( '' === $msg ) {
250 return $this->WPDA_Rest_Response( __( 'Table successfully truncated', 'wp-data-access' ) );
251 } else {
252 return new \WP_Error('error', $msg, array(
253 'status' => 403,
254 ));
255 }
256 }
257
258 public function action_copy( $request ) {
259 if ( !$this->current_user_can_access() ) {
260 return $this->unauthorized();
261 }
262 if ( !$this->current_user_token_valid( $request ) ) {
263 return $this->invalid_nonce();
264 }
265 $from_dbs = $request->get_param( 'from_dbs' );
266 $to_dbs = $request->get_param( 'to_dbs' );
267 $from_tbl = $request->get_param( 'from_tbl' );
268 $to_tbl = $request->get_param( 'to_tbl' );
269 $copy_data = $request->get_param( 'copy_data' );
270 if ( '' === $from_dbs || '' === $to_dbs || '' === $from_tbl || '' === $to_tbl ) {
271 return $this->bad_request();
272 }
273 $msg = $this->copy(
274 $from_dbs,
275 $to_dbs,
276 $from_tbl,
277 $to_tbl,
278 $copy_data
279 );
280 if ( '' === $msg ) {
281 return $this->WPDA_Rest_Response( __( 'Table successfully copied', 'wp-data-access' ) );
282 } else {
283 return new \WP_Error('error', $msg, array(
284 'status' => 403,
285 ));
286 }
287 }
288
289 public function action_rename( $request ) {
290 if ( !$this->current_user_can_access() ) {
291 return $this->unauthorized();
292 }
293 if ( !$this->current_user_token_valid( $request ) ) {
294 return $this->invalid_nonce();
295 }
296 $dbs = $request->get_param( 'dbs' );
297 $from_tbl = $request->get_param( 'from_tbl' );
298 $to_tbl = $request->get_param( 'to_tbl' );
299 $typ = $request->get_param( 'typ' );
300 if ( '' === $dbs || '' === $from_tbl || '' === $to_tbl ) {
301 return $this->bad_request();
302 }
303 if ( 'information_schema' === $dbs || 'mysql' === $dbs || 'performance_schema' === $dbs || 'sys' === $dbs || '' === $dbs ) {
304 return $this->unauthorized();
305 }
306 global $wpdb;
307 if ( $wpdb->dbname === $dbs && in_array( $from_tbl, $wpdb->tables() ) ) {
308 return $this->unauthorized();
309 }
310 $msg = $this->rename( $dbs, $from_tbl, $to_tbl );
311 if ( '' === $msg ) {
312 if ( 1 === $typ ) {
313 return $this->WPDA_Rest_Response( __( 'View successfully renamed', 'wp-data-access' ) );
314 } else {
315 return $this->WPDA_Rest_Response( __( 'Table successfully renamed', 'wp-data-access' ) );
316 }
317 } else {
318 return new \WP_Error('error', $msg, array(
319 'status' => 403,
320 ));
321 }
322 }
323
324 private function rename( $dbs, $from_tbl, $to_tbl ) {
325 // All values have already been validated and sanitized in the rest route registration.
326 if ( !WPDA::current_user_is_admin() ) {
327 return 'Unauthorized';
328 }
329 $wpdadb = WPDADB::get_db_connection( $dbs );
330 if ( null === $wpdadb ) {
331 return sprintf( __( 'Remote database %s not available', 'wp-data-access' ), esc_attr( $dbs ) );
332 }
333 $suppress_errors = $wpdadb->suppress_errors;
334 $wpdadb->suppress_errors = true;
335 $wpdadb->query( $wpdadb->prepare( 'rename table `%1s` to `%1s`', array($from_tbl, $to_tbl) ) );
336 $wpdadb->suppress_errors = $suppress_errors;
337 return $wpdadb->last_error;
338 }
339
340 private function copy(
341 $from_dbs,
342 $to_dbs,
343 $from_tbl,
344 $to_tbl,
345 $copy_data
346 ) {
347 // All values have already been validated and sanitized in the rest route registration.
348 if ( !WPDA::current_user_is_admin() ) {
349 return 'Unauthorized';
350 }
351 $wpdadb_from = WPDADB::get_db_connection( $from_dbs );
352 if ( null === $wpdadb_from ) {
353 return sprintf( __( 'Remote database %s not available', 'wp-data-access' ), esc_attr( $from_dbs ) );
354 }
355 $wpdadb_to = WPDADB::get_db_connection( $to_dbs );
356 if ( null === $wpdadb_to ) {
357 return sprintf( __( 'Remote database %s not available', 'wp-data-access' ), esc_attr( $to_dbs ) );
358 }
359 $suppress_errors_from = $wpdadb_from->suppress_errors;
360 $wpdadb_from->suppress_errors = true;
361 $suppress_errors_to = $wpdadb_to->suppress_errors;
362 $wpdadb_to->suppress_errors = true;
363 // Get create table statement.
364 // NO_TABLE_OPTIONS is deprecated in V8
365 // $wpdadb_from->query( "SET sql_mode = 'NO_TABLE_OPTIONS'" );
366 $sql_cmd = $wpdadb_from->get_results( $wpdadb_from->prepare( 'show create table `%1s`', array($from_tbl) ), 'ARRAY_A' );
367 // Check for errors.
368 if ( '' !== $wpdadb_from->last_error ) {
369 $wpdadb_from->suppress_errors = $suppress_errors_from;
370 $wpdadb_to->suppress_errors = $suppress_errors_to;
371 return $wpdadb_from->last_error;
372 }
373 if ( !isset( $sql_cmd[0]['Create Table'] ) ) {
374 $wpdadb_from->suppress_errors = $suppress_errors_from;
375 $wpdadb_to->suppress_errors = $suppress_errors_to;
376 return 'Create command table failed';
377 }
378 // Update destination table name if applicable.
379 $create_table_statement = $sql_cmd[0]['Create Table'];
380 if ( $from_tbl !== $to_tbl ) {
381 // Modify create table statement
382 $pos = strpos( $create_table_statement, $from_tbl );
383 if ( $pos !== false ) {
384 $create_table_statement = substr_replace(
385 $create_table_statement,
386 $to_tbl,
387 $pos,
388 strlen( $from_tbl )
389 );
390 }
391 }
392 // Create new table.
393 $wpdadb_to->query( $create_table_statement );
394 // Check for errors.
395 if ( '' !== $wpdadb_to->last_error ) {
396 $wpdadb_from->suppress_errors = $suppress_errors_from;
397 $wpdadb_to->suppress_errors = $suppress_errors_to;
398 return $wpdadb_to->last_error;
399 }
400 if ( '1' === $copy_data ) {
401 // Copy data from source to destination table.
402 set_time_limit( 0 );
403 // Prevent time out.
404 // Use a cursor to process all rows and prevent exhausting memory.
405 // Process 100 rows per batch to prevent exhausting memory.
406 $buffer_size = 100;
407 $index = 0;
408 $loop_done = false;
409 while ( !$loop_done ) {
410 // Get rows.
411 $rows = $wpdadb_from->get_results( $wpdadb_from->prepare( 'select * from `%1s` limit %1s offset %1s', array($from_tbl, $buffer_size, $index * $buffer_size) ), 'ARRAY_A' );
412 // Process rows.
413 foreach ( $rows as $row ) {
414 $wpdadb_to->insert( $to_tbl, $row );
415 }
416 if ( 100 > count( $rows ) ) {
417 // No more rows to process.
418 $loop_done = true;
419 }
420 $index++;
421 }
422 }
423 $wpdadb_from->suppress_errors = $suppress_errors_from;
424 $wpdadb_to->suppress_errors = $suppress_errors_to;
425 return '';
426 }
427
428 private function truncate( $dbs, $tbl ) {
429 // All values have already been validated and sanitized in the rest route registration.
430 if ( !WPDA::current_user_is_admin() ) {
431 return 'Unauthorized';
432 }
433 $wpdadb = WPDADB::get_db_connection( $dbs );
434 if ( null === $wpdadb ) {
435 return sprintf( __( 'Remote database %s not available', 'wp-data-access' ), esc_attr( $dbs ) );
436 }
437 $suppress_errors = $wpdadb->suppress_errors;
438 $wpdadb->suppress_errors = true;
439 $wpdadb->query( $wpdadb->prepare( 'truncate table `%1s`', array($tbl) ) );
440 $wpdadb->suppress_errors = $suppress_errors;
441 return $wpdadb->last_error;
442 }
443
444 private function drop( $dbs, $tbl, $typ ) {
445 // All values have already been validated and sanitized in the rest route registration.
446 if ( !WPDA::current_user_is_admin() ) {
447 return 'Unauthorized';
448 }
449 $wpdadb = WPDADB::get_db_connection( $dbs );
450 if ( null === $wpdadb ) {
451 return sprintf( __( 'Remote database %s not available', 'wp-data-access' ), esc_attr( $dbs ) );
452 }
453 $suppress_errors = $wpdadb->suppress_errors;
454 $wpdadb->suppress_errors = true;
455 if ( 1 === $typ ) {
456 $wpdadb->query( $wpdadb->prepare( 'drop view `%1s`', array($tbl) ) );
457 } else {
458 $wpdadb->query( $wpdadb->prepare( 'drop table `%1s`', array($tbl) ) );
459 }
460 $this->post_drop_table( $dbs, $tbl );
461 $wpdadb->suppress_errors = $suppress_errors;
462 return $wpdadb->last_error;
463 }
464
465 private function post_drop_table( $dbs, $tbl ) {
466 global $wpdb;
467 $suppress = $wpdb->suppress_errors( true );
468 // Table settings...
469 $wpdb->query( $wpdb->prepare(
470 'delete from `%1s` where wpda_schema_name = %s and wpda_table_name = %s ',
471 // phpcs:ignore WordPress.DB.PreparedSQLPlaceholders
472 array(WPDA::remove_backticks( WPDA_Table_Settings_Model::get_base_table_name() ), $dbs, $tbl)
473 ) );
474 // WordPress media library columns...
475 $wpdb->query( $wpdb->prepare(
476 'delete from `%1s` where media_schema_name = %s and media_table_name = %s ',
477 // phpcs:ignore WordPress.DB.PreparedSQLPlaceholders
478 array(WPDA::remove_backticks( WPDA_Media_Model::get_base_table_name() ), $dbs, $tbl)
479 ) );
480 // Data menus...
481 $wpdb->query( $wpdb->prepare(
482 'delete from `%1s` where menu_schema_name = %s and menu_table_name = %s ',
483 // phpcs:ignore WordPress.DB.PreparedSQLPlaceholders
484 array(WPDA::remove_backticks( WPDA_User_Menus_Model::get_base_table_name() ), $dbs, $tbl)
485 ) );
486 $wpdb->suppress_errors( $suppress );
487 }
488
489 private function import( $file_name, $dbs ) {
490 if ( !WPDA::current_user_is_admin() ) {
491 return array(
492 'status' => 'error',
493 'msg' => 'Unauthorized',
494 );
495 }
496 $errors = array();
497 global $wpdb;
498 $wpdadb = WPDADB::get_db_connection( $dbs );
499 if ( null === $wpdadb ) {
500 return array(
501 'status' => 'error',
502 'msg' => sprintf( __( 'ERROR - Remote database %s not available', 'wp-data-access' ), esc_attr( $dbs ) ),
503 );
504 }
505 $suppress = $wpdadb->suppress_errors( true );
506 if ( false !== $this->file_pointer ) {
507 while ( !feof( $this->file_pointer ) ) {
508 $this->file_content .= fread( $this->file_pointer, 4096 );
509 // Replace WP prefix and WPDA prefix.
510 $this->file_content = str_replace( '{wp_schema}', $wpdb->dbname, $this->file_content );
511 $this->file_content = str_replace( '{wp_prefix}', $wpdb->prefix, $this->file_content );
512 $this->file_content = str_replace( '{wpda_prefix}', 'wpda', $this->file_content );
513 // for backward compatibility
514 // Find and process SQL statements.
515 $sql_end_unix = strpos( $this->file_content, ";\n" );
516 $sql_end_windows = strpos( $this->file_content, ";\r\n" );
517 while ( false !== $sql_end_unix || false !== $sql_end_windows ) {
518 if ( false === $sql_end_unix ) {
519 $sql_end = $sql_end_windows;
520 } elseif ( false === $sql_end_windows ) {
521 $sql_end = $sql_end_unix;
522 } else {
523 $sql_end = min( $sql_end_unix, $sql_end_windows );
524 }
525 $sql = rtrim( substr( $this->file_content, 0, $sql_end ) );
526 $this->file_content = substr( $this->file_content, strpos( $this->file_content, $sql ) + strlen( $sql ) + 1 );
527 if ( false === $wpdadb->query( $sql ) ) {
528 if ( '' !== $wpdadb->last_error ) {
529 $errors[] = $wpdadb->last_error;
530 }
531 }
532 // Find next SQL statement.
533 $sql_end_unix = strpos( $this->file_content, ";\n" );
534 $sql_end_windows = strpos( $this->file_content, ";\r\n" );
535 }
536 }
537 }
538 $wpdadb->suppress_errors( $suppress );
539 // Process file content.
540 if ( 0 < count( $errors ) ) {
541 return array(
542 'status' => 'error',
543 'msg' => sprintf( __( 'Import `%s` failed [check import file]', 'wp-data-access' ), $file_name ),
544 'errors' => $errors,
545 );
546 } else {
547 // Import succeeded.
548 return array(
549 'status' => 'ok',
550 'msg' => sprintf( __( 'Import `%s` completed succesfully', 'wp-data-access' ), $file_name ),
551 'errors' => $errors,
552 );
553 }
554 }
555
556 }
557