<?php
/**
 * Genesis Blocks post content migration.
 *
 * @package Genesis\Blocks\Migration
 * @since   1.1.0
 * @author  StudioPress
 * @license GPL-2.0-or-later
 * @link    https://github.com/studiopress/genesis-blocks/
 */

declare(strict_types=1);
namespace Genesis\Blocks\Migration;

use WP_Error;

/**
 * Migrates the post content.
 *
 * @since 1.1.0
 */
final class PostContent {

	/**
	 * Migrates batches of Atomic Blocks blocks in posts that have them.
	 *
	 * Sets a timeout to avoid PHP timeouts. The migration app will request
	 * migrations in batches until the number of posts found is zero.
	 *
	 * @return array|WP_Error The result of the migration, or a WP_Error if it failed.
	 */
	public function migrate_batch() {
		$success_count      = 0;
		$error_count        = 0;
		$errors             = new WP_Error();
		$max_allowed_errors = 20;
		$posts              = $this->query_for_posts();
		$time_start         = microtime( true );
		$timeout            = 10;

		while (
			! empty( $posts )
			&& $error_count < $max_allowed_errors
			&& microtime( true ) - $time_start <= $timeout
		) {
			foreach ( $posts as $post ) {
				if ( isset( $post->ID ) ) {
					$migrated_post = $this->migrate_single( $post->ID );
					if ( is_wp_error( $migrated_post ) ) {
						$error_count++;
						$errors->add( $migrated_post->get_error_code(), $migrated_post->get_error_message() );
					} else {
						$success_count++;
					}
				}
			}

			$posts = $this->query_for_posts();
		}

		$is_success = $error_count < $max_allowed_errors;
		if ( ! $is_success ) {
			return $errors;
		}

		$results = [
			'successCount' => $success_count,
			'errorCount'   => $error_count,
			'postsFound'   => $success_count + $error_count,
		];

		if ( $errors->has_errors() ) {
			$results['errorMessage'] = $errors->get_error_message();
		}

		return $results;
	}

	/**
	 * Migrates the block namespaces in post_content.
	 *
	 * Blocks are stored in the post_content of a post with a namespace,
	 * like '<!-- wp:atomic-blocks/ab-accordion /-->'.
	 * In that case, 'atomic-blocks/ab' needs to be changed to the new namespace.
	 * But nothing else in the block should be changed.
	 * The block pattern is mainly taken from Core.
	 *
	 * @see https://github.com/WordPress/wordpress-develop/blob/78d1ab2ed40093a5bd2a75b01ceea37811739f55/src/wp-includes/class-wp-block-parser.php#L413
	 *
	 * @param int $post_id The ID of the post to convert.
	 * @return int|WP_Error The post ID that was changed, or a WP_Error on failure.
	 */
	public function migrate_single( $post_id ) {
		$post = get_post( $post_id );
		if ( ! isset( $post->ID ) ) {
			return new WP_Error(
				'invalid_post_id',
				__( 'Invalid post ID', 'genesis-blocks' )
			);
		}

		$new_content = $this->migrate_post_content( $post->post_content );

		return wp_update_post(
			[
				'ID'           => $post->ID,
				'post_content' => wp_slash( $new_content ),
			],
			true
		);
	}

	/**
	 * Migrates the existing post content to the
	 * format used by Genesis Blocks.
	 *
	 * @param string $content The post content to migrate.
	 *
	 * @return string
	 */
	public function migrate_post_content( string $content ): string {
		$new_content = $content;

		// The newsletter block didn't follow the existing naming convention, so let's target it specifically.
		$new_content = str_replace( 'atomic-blocks/newsletter', 'genesis-blocks/gb-newsletter', $new_content );

		// Block names.
		$new_content = str_replace( 'atomic-blocks/ab', 'genesis-blocks/gb', $new_content );

		// HTML class name generated by WordPress.
		$new_content = str_replace( 'wp-block-atomic-blocks-ab', 'wp-block-genesis-blocks-gb', $new_content );

		// Catch all.
		$new_content = str_replace( 'atomic-blocks', 'genesis-blocks', $new_content );

		// Replace exact matches for known Atomic Blocks strings prefixed with "ab-".
		$known_strings = [
			'ab-1-col',
			'ab-2-col',
			'ab-3-col',
			'ab-4-col',
			'ab-5-col',
			'ab-6-col',
			'ab-accordion',
			'ab-add-image',
			'ab-align',
			'ab-author-profile',
			'ab-background',
			'ab-block',
			'ab-button',
			'ab-change-image',
			'ab-column',
			'ab-container',
			'ab-cta',
			'ab-dismissable',
			'ab-divider',
			'ab-drop-cap',
			'ab-font-size',
			'ab-form-styles',
			'ab-has-avatar',
			'ab-has-background-dim',
			'ab-has-custom',
			'ab-has-parallax',
			'ab-is-responsive-column',
			'ab-is-vertically-aligned',
			'ab-layout',
			'ab-list',
			'ab-newsletter',
			'ab-notice',
			'ab-post-grid',
			'ab-pricing',
			'ab-profile',
			'ab-share',
			'ab-social-links',
			'ab-social-text',
			'ab-spacer',
			'ab-testimonial',
			'ab-white-text',
		];

		$replacements = str_replace( 'ab-', 'gb-', $known_strings );

		$new_content = str_replace( $known_strings, $replacements, $new_content );

		return $new_content;
	}

	/**
	 * Gets posts that have Atomic Blocks blocks in their post_content.
	 *
	 * Queries for posts that have wp:atomic-blocks/ in the post content,
	 * meaning they probably have an Atomic Blocks block.
	 * Excludes revision posts, as this could overwrite the entire history.
	 * This will allow users to go back to the content before it was migrated.
	 *
	 * @return array The posts that were found.
	 */
	public function query_for_posts() {
		global $wpdb;

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		return $wpdb->get_results(
			$wpdb->prepare(
				"SELECT * FROM {$wpdb->posts} WHERE post_type != %s AND post_content LIKE %s LIMIT %d",
				'revision',
				'%wp:atomic-blocks/%',
				10
			)
		);
	}
}
