Form_Post_Type::POST_TYPE, 'post_status' => 'any', 'posts_per_page' => $limit, 'fields' => 'ids', 'orderby' => 'ID', 'order' => 'ASC', 'no_found_rows' => true, 'meta_query' => array( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Runs once per site, in batches. 'relation' => 'AND', array( 'key' => Form_Post_Type::META_SCHEMA, 'compare' => 'NOT EXISTS', ), array( 'key' => self::META_ERROR, 'compare' => 'NOT EXISTS', ), ), ) ); foreach ( $ids as $id ) { self::convert_form( (int) $id ); } return count( $ids ); } /** * Converts one form's template and stores the result: the schema on * success, the reasons (and no schema) on failure. * * @param int $form_id Form post ID. * @param Form_Schema|null $keep Previous schema whose notifications, * confirmations and settings carry over * (the tags only describe the fields). */ public static function convert_form( int $form_id, ?Form_Schema $keep = null ): bool { $template = get_post_meta( $form_id, Form_Post_Type::META_TEMPLATE, true ); $meta = self::conversion_meta( is_string( $template ) ? $template : '', $keep ); self::write_meta( $form_id, $meta ); return null === $meta[ self::META_ERROR ]; } /** * The meta a template's conversion writes: the schema and its source on * success, the reasons and no schema on failure. Null deletes a key. * * @param string $template Template ('' for the default one). * @param Form_Schema|null $keep Previous schema, as for convert_form(). * @return array */ public static function conversion_meta( string $template, ?Form_Schema $keep = null ): array { $converter = new Form_Converter(); $schema = $converter->convert( '' !== $template ? $template : Form::default_template() ); if ( null === $schema ) { return array( Form_Post_Type::META_SCHEMA => null, self::META_SOURCE => null, self::META_ERROR => $converter->errors(), ); } if ( null !== $keep ) { $kept = array_intersect_key( $keep->to_array(), array_flip( array( 'notifications', 'confirmations', 'settings' ) ) ); $schema = Form_Schema::from_array( $schema->to_array() + $kept ) ?? $schema; } return array( Form_Post_Type::META_SCHEMA => $schema->to_json(), self::META_SOURCE => 'template', self::META_ERROR => null, ); } /** * Stores a schema saved by the editor, with its tags as the template * backup (so an older plugin version still shows the same form). * * @param int $form_id Form post ID. * @param Form_Schema $schema Sanitized schema. */ public static function store( int $form_id, Form_Schema $schema ): void { self::write_meta( $form_id, self::store_meta( $schema ) ); } /** * The meta store() writes. * * @param Form_Schema $schema Sanitized schema. * @return array */ public static function store_meta( Form_Schema $schema ): array { return array( Form_Post_Type::META_SCHEMA => $schema->to_json(), Form_Post_Type::META_TEMPLATE => Form::sanitize_template( $schema->template() ), self::META_SOURCE => 'builder', self::META_ERROR => null, ); } /** * Writes meta values to a form; a null value deletes the key. * * @param int $form_id Form post ID. * @param array $meta Meta key => value (unslashed). */ public static function write_meta( int $form_id, array $meta ): void { foreach ( $meta as $key => $value ) { if ( null === $value ) { delete_post_meta( $form_id, $key ); } else { update_post_meta( $form_id, $key, wp_slash( $value ) ); } } } /** * The schema the editor starts from: the stored one, or for a form that * has none yet (a new form) its template converted on the fly. Null when * the template cannot be converted; the editor then edits tags only. * * @param Form $form Form. */ public static function editor_schema( Form $form ): ?Form_Schema { if ( null !== $form->schema ) { return $form->schema; } if ( $form->id > 0 && array() !== self::errors( $form->id ) ) { return null; } return ( new Form_Converter() )->convert( $form->template ); } /** * Why a form's template could not be converted (empty when it was). * * @param int $form_id Form post ID. * @return string[] */ public static function errors( int $form_id ): array { $errors = get_post_meta( $form_id, self::META_ERROR, true ); return is_array( $errors ) ? array_map( 'strval', $errors ) : array(); } /** * Number of forms that could not be converted. */ public static function failed_count(): int { $ids = get_posts( array( 'post_type' => Form_Post_Type::POST_TYPE, 'post_status' => 'any', 'posts_per_page' => -1, 'fields' => 'ids', 'no_found_rows' => true, 'meta_key' => self::META_ERROR, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Admin screen only. ) ); return count( $ids ); } }