,

Gemini-Assisted WordPress Plugin: Corrections 2

This continues the work in the post Gemini-Assisted WordPress Plugin: Corrections. We complete the admin by linking the ID to the post, fixing the Delete feature, adding a CAPTCHA and a settings screen.

The code is at the bottom of the page, and it’s a mess, but it seems to work for me.

I reopened the chat I was using to code this up, and then repasted the code into the chat, telling Gemini:

Here's the current version of my code. Base all the following responses on this codebase: <pasted code here>

Here’s a screenshot of my admin screen:

The ID wasn’t linked to anything, so I asked for code to link it to the original post. It correctly linked to open the post editor, and worked perfectly.

Delete

The delete link was broken, going to the wrong page after deletion. I asked to go back to the edit screen, and it corrected the code!

CAPTCHA

I asked about captcha options, and it suggested the latest Google CAPTCHA. OK, I can live with that. It’s not what I really want, but it’s simple, and it generated the code.

However, it had hardcoded keys. I asked Gemini to generate some options pages for these settings. It generated the pages, and also generated new code for the forms and the form handler.

I haven’t had a chance to test this live.

Gemini made mistakes in the menus, again. I wanted a page in the Settings, but also wanted a link from the Correction menu to the Correction Settings page. Gemini just didn’t do it right, so I had to fix the code myself.

The menu system was kind of difficult to understand, because it was designed to use a lot of sensible defaults, lots of conventions, and so forth. So, it’s a kind of coder-friendly shorthand; there’s a learning curve because it’s not explicit, but once you know it, it’s comprehensible, and easy to write.

All this work, including writing this blog post, took only an hour!

Update: I used PHP Codesniffer to reformat this code to look more like the WordPress standard. I originally asked Gemini to reformat it, but I didn’t know if I could trust the output!

After running phpcbf to fix the code, I still had a bunch of errors. To manually edit away those errors, I will use this command to show me all the remaining errors:

watch "phpcs -- --standard=WordPress ./plugin.php | tail -n +6"
<?php
/*
Plugin Name: Correction Feedback Plugin
Description: A very basic plugin example.
Version: 1.0
Author: Your Name
*/

// Shortcode to display the link
function correction_link_shortcode() {
	global $post;
	if ( ! $post ) {
		return '';
	}
	$form_url  = add_query_arg(
		array(
			'correction_form' => 'true',
			'post_id'         => $post->ID,
		),
		get_permalink( get_option( 'correction_form_page_id' ) )
	);
	$icon      = '[]';
	$link_text = 'Submit a Correction ';
	$link      = '<a href="' . esc_url( $form_url ) . '" target="_blank">' . esc_html( $link_text ) . $icon . '</a>';
	return $link;
}

// Shortcode to display the form
function correction_form_shortcode() {
	if ( isset( $_GET['correction_form'] ) && $_GET['correction_form'] == 'true' ) {
		ob_start();

		$post_id = isset( $_GET['post_id'] ) ? intval( $_GET['post_id'] ) : 0;

		if ( $post_id <= 0 ) {
			echo '<p>Invalid post ID.</p>';
			return ob_get_clean();
		}

		if ( $_SERVER['REQUEST_METHOD'] === 'GET' ) {
			// Replace with your actual site key
			$recaptcha_site_key = esc_attr( get_option( 'correction_recaptcha_site_key' ) );

			echo '<form method="post">';
			echo '<input type="hidden" name="post_id" value="' . esc_attr( $post_id ) . '">';
			echo '<label for="original_sentence">Original Sentence (Paste Here):</label><br>';
			echo '<textarea name="original_sentence" id="original_sentence" rows="4" cols="50" required></textarea><br><br>';
			echo '<label for="correction">Your Correction:</label><br>';
			echo '<textarea name="correction-text" id="correction" rows="4" cols="50" required></textarea><br><br>';
			echo '<label for="email">Your Email:</label><br>';
			echo '<input type="email" name="correction-email" id="email"><br><br>';

			// Hidden reCAPTCHA token field
			echo '<input type="hidden" name="recaptcha_token" id="recaptcha_token">';

			echo '<input type="submit" value="Submit">';
			echo '</form>';

			// reCAPTCHA JavaScript
			if ( ! empty( $recaptcha_site_key ) ) { // Only include script if site key is set.
				echo '<script src="https://www.google.com/recaptcha/api.js?render=' . esc_attr( $recaptcha_site_key ) . '"></script>';
				echo '<script>';
				echo 'grecaptcha.ready(function() {';
				echo 'grecaptcha.execute("' . esc_attr( $recaptcha_site_key ) . '", {action: "correction_form"}).then(function(token) {';
				echo 'document.getElementById("recaptcha_token").value = token;';
				echo '});';
				echo '});';
				echo '</script>';
			}

			return ob_get_clean();
		} else {
			return correction_form_handler();
		}
	}
}

// Handle the submission
function correction_form_handler() {
	if ( $_SERVER['REQUEST_METHOD'] === 'POST' ) {
		ob_start();

		$original_sentence = isset( $_POST['original_sentence'] ) ? sanitize_textarea_field( $_POST['original_sentence'] ) : '';
		$correction        = isset( $_POST['correction-text'] ) ? sanitize_textarea_field( $_POST['correction-text'] ) : '';
		$email             = isset( $_POST['correction-email'] ) ? sanitize_email( $_POST['correction-email'] ) : '';
		$post_id           = isset( $_POST['post_id'] ) ? intval( $_POST['post_id'] ) : 0;

		if ( empty( $original_sentence ) || empty( $correction ) || empty( $email ) ) {
			echo '<p>Please fill in all required fields.</p>';
			return ob_get_clean();
		}

		$recaptcha_token = isset( $_POST['recaptcha_token'] ) ? sanitize_text_field( $_POST['recaptcha_token'] ) : '';

		$recaptcha_secret_key = esc_attr( get_option( 'correction_recaptcha_secret_key' ) );

		if ( ! empty( $recaptcha_secret_key ) ) { // Only verify if secret key is set.
			$recaptcha_url      = 'https://www.google.com/recaptcha/api/siteverify';
			$recaptcha_response = wp_remote_post(
				$recaptcha_url,
				array(
					'body' => array(
						'secret'   => $recaptcha_secret_key,
						'response' => $recaptcha_token,
					),
				)
			);

			if ( is_wp_error( $recaptcha_response ) ) {
				echo '<p>reCAPTCHA verification failed.</p>';
				return ob_get_clean();
			}

			$recaptcha_body = json_decode( wp_remote_retrieve_body( $recaptcha_response ) );

			if ( ! $recaptcha_body->success || $recaptcha_body->score < 0.5 ) { // Adjust score threshold as needed
				echo '<p>reCAPTCHA verification failed. You might be a bot.</p>';
				return ob_get_clean();
			}
		}

		// Create a new correction post
		$correction_post = array(
			'post_type'    => 'correction',
			'post_status'  => 'pending',
			'post_title'   => 'Correction for Post ID: ' . $post_id,
			'post_content' => "Original Sentence:\n" . $original_sentence . "\n\nCorrection:\n" . $correction . "\n\nEmail: " . $email,
			'meta_input'   => array(
				'original_post_id'  => $post_id,
				'original_sentence' => $original_sentence,
				'correction'        => $correction,
				'email'             => $email,
			),
		);

		$result = wp_insert_post( $correction_post, true );

		// Send email notification to the administrator
		$admin_email = get_option( 'admin_email' );
		$subject     = 'New Correction Submitted';
		$message     = "A new correction has been submitted for post ID: {$post_id}\n\n";
		$message    .= "Original Sentence:\n{$original_sentence}\n\n";
		$message    .= "Correction:\n{$correction}\n\n";
		$message    .= "Email: {$email}\n";

		// wp_mail($admin_email, $subject, $message);

		echo '<p>Thank you for your correction! It has been submitted.</p>';
		echo "<p>ID: $result</p>";
		return ob_get_clean();
	}
}

// Plugin Activation Function
function correction_plugin_activate() {

	$page_title   = 'Submit a Correction';
	$page_content = '[correction_form]';

	$page_exists = get_page_by_title( $page_title );

	if ( ! $page_exists ) {
		$page_id = wp_insert_post(
			array(
				'post_title'   => $page_title,
				'post_content' => $page_content,
				'post_status'  => 'publish',
				'post_type'    => 'page',
			)
		);
		if ( $page_id ) {
			update_option( 'correction_form_page_id', $page_id );
		}
	} else {
		update_option( 'correction_form_page_id', $page_exists->ID );
	}
	flush_rewrite_rules();
}
register_activation_hook( __FILE__, 'correction_plugin_activate' );


// Plugin Deactivation Function
function correction_plugin_deactivate() {
	unregister_post_type( 'correction' );
	delete_option( 'correction_form_page_id' );
	flush_rewrite_rules();
}
register_deactivation_hook( __FILE__, 'correction_plugin_deactivate' );

// Custom post type
function register_correction_post_type() {
	register_post_type(
		'correction', // $post_type
		array(
			'labels'          => array(
				'name'               => __( 'Corrections', 'textdomain' ),
				'singular_name'      => __( 'Correction', 'textdomain' ),
				'menu_name'          => __( 'Corrections', 'textdomain' ),
				'all_items'          => __( 'All Corrections', 'textdomain' ),
				'add_new'            => __( 'Add New', 'textdomain' ),
				'add_new_item'       => __( 'Add New Correction', 'textdomain' ),
				'edit_item'          => __( 'Edit Correction', 'textdomain' ),
				'new_item'           => __( 'New Correction', 'textdomain' ),
				'view_item'          => __( 'View Correction', 'textdomain' ),
				'search_items'       => __( 'Search Corrections', 'textdomain' ),
				'not_found'          => __( 'No corrections found', 'textdomain' ),
				'not_found_in_trash' => __( 'No corrections found in Trash', 'textdomain' ),
			),
			'public'          => true, // Show in admin menu
			'show_ui'         => true, // Show admin UI
			'show_in_menu'    => true, // show in the admin menu.
			'capability_type' => 'correction',
			'hierarchical'    => false,
			'rewrite'         => array( 'slug' => 'correction' ),
			'query_var'       => true,
			'supports'        => array( 'title', 'editor' ),
			'menu_position'   => 30,
			'has_archive'     => false, // No archive page
		)
	);
}

function correction_admin_menu() {
	add_menu_page(
		'Corrections', // Page title
		'Corrections', // Menu title
		'edit_posts', // Capability
		'correction', // Menu slug
		'correction_admin_page', // Callback function
		'dashicons-edit',
		30
	);
}
// Add settings submenu
function correction_list_submenu() {
	add_submenu_page(
		'correction', // Parent slug (the main Corrections menu slug)
		'Corrections', // Page title
		'Corrections', // Menu title
		'edit_posts', // Capability
		'correction', // Menu slug
		'correction_admin_page' // Callback function
	);
}
add_action( 'admin_menu', 'correction_list_submenu' );

// Add settings submenu
function correction_add_settings_submenu() {
	add_submenu_page(
		'correction', // Parent slug (the main Corrections menu slug)
		'Correction Settings', // Page title
		'Settings', // Menu title
		'manage_options', // Capability
		'options-general.php?page=correction-settings', // Menu slug
		'correction_settings_page' // Callback function
	);
}
add_action( 'admin_menu', 'correction_add_settings_submenu' );


// Admin page content
function correction_admin_page() {
	?>
	<div class="wrap">
		<h2>Corrections</h2>
		<table class="wp-list-table widefat fixed striped posts">
			<thead>
				<tr>
					<th>Post ID</th>
					<th>Original Sentence</th>
					<th>Correction</th>
					<th>Email</th>
					<th>Actions</th>
				</tr>
			</thead>
			<tbody>
				<?php
				$corrections = get_posts(
					$args    = array(
						'post_type'   => 'correction',
						'post_status' => 'any',
						'numberposts' => 10,
					)
				);

				foreach ( $corrections as $correction ) {
					$original_post_id   = get_post_meta( $correction->ID, 'original_post_id', true );
					$original_sentence  = get_post_meta( $correction->ID, 'original_sentence', true );
					$corrected_sentence = get_post_meta( $correction->ID, 'correction', true );
					$email              = get_post_meta( $correction->ID, 'email', true );
					$status             = $correction->post_status;

					// Check if the original post exists
					$original_post = get_post( $original_post_id );
					if ( $original_post ) {
						$original_post_link = '<a href="' . esc_url( get_edit_post_link( $original_post_id ) ) . '">' . esc_html( $original_post_id ) . '</a>';
					} else {
						$original_post_link = 'Post ID: ' . esc_html( $original_post_id ) . ' (Not Found)';
					}
					?>
					<tr>
						<td><?php echo $original_post_link; ?></td>                  
						<td><?php echo esc_html( $original_sentence ); ?></td>
						<td><?php echo esc_html( $corrected_sentence ); ?></td>
						<td><?php echo esc_html( $email ); ?></td>
						<td>
							<a href="
							<?php
							echo esc_url(
								add_query_arg(
									array(
										'action'        => 'delete_correction',
										'correction_id' => $correction->ID,
									)
								)
							);
							?>
							">Delete</a>
						</td>
					</tr>
				<?php } ?>
			</tbody>
		</table>
	</div>
	<?php
}

// Handle delete actions
function handle_correction_actions() {
	if ( isset( $_GET['action'] ) && $_GET['action'] === 'delete_correction' && isset( $_GET['correction_id'] ) ) {
		$correction_id = intval( $_GET['correction_id'] );

		if ( $correction_id ) {
			wp_delete_post( $correction_id, true ); // true = force delete

			// Redirect back to the corrections admin page
			wp_redirect( admin_url( 'admin.php?page=correction' ) ); // Ensure to use admin.php?page=correction for menu slug.
			exit;
		}
	}
}



// Add settings menu
function correction_add_settings_page() {
	add_options_page(
		'Correction Settings',
		'Correction',
		'manage_options',
		'correction-settings',
		'correction_settings_page'
	);
}
add_action( 'admin_menu', 'correction_add_settings_page' );

// Settings page content
function correction_settings_page() {
	?>
	<div class="wrap">
		<h2>Correction Settings</h2>
		<form method="post" action="options.php">
			<?php
			settings_fields( 'correction_settings_group' );
			do_settings_sections( 'correction-settings' );
			submit_button();
			?>
		</form>
	</div>
	<?php
}

// Register settings
function correction_register_settings() {
	register_setting( 'correction_settings_group', 'correction_recaptcha_site_key' );
	register_setting( 'correction_settings_group', 'correction_recaptcha_secret_key' );

	add_settings_section(
		'correction_recaptcha_settings',
		'reCAPTCHA Settings',
		'correction_recaptcha_settings_callback',
		'correction-settings'
	);

	add_settings_field(
		'correction_recaptcha_site_key',
		'reCAPTCHA Site Key',
		'correction_recaptcha_site_key_callback',
		'correction-settings',
		'correction_recaptcha_settings'
	);

	add_settings_field(
		'correction_recaptcha_secret_key',
		'reCAPTCHA Secret Key',
		'correction_recaptcha_secret_key_callback',
		'correction-settings',
		'correction_recaptcha_settings'
	);
}
add_action( 'admin_init', 'correction_register_settings' );

function correction_recaptcha_settings_callback() {
	echo 'Enter your reCAPTCHA v3 site key and secret key.';
}

function correction_recaptcha_site_key_callback() {
	$site_key = esc_attr( get_option( 'correction_recaptcha_site_key' ) );
	echo "<input type='text' name='correction_recaptcha_site_key' value='$site_key' size='50' />";
}

function correction_recaptcha_secret_key_callback() {
	$secret_key = esc_attr( get_option( 'correction_recaptcha_secret_key' ) );
	echo "<input type='text' name='correction_recaptcha_secret_key' value='$secret_key' size='50' />";
}

// INIT SECTION

// Admin notice error shown when permalinks are off.
function correction_permalink_notice() {
	?>
	<div class="notice notice-error">
		<p><?php _e( 'The Correction Plugin requires using permalinks. Please enable permalinks in your WordPress settings.', 'textdomain' ); ?></p>
	</div>
	<?php
}

function correction_init() {
	global $wp_rewrite;
	if ( ! $wp_rewrite->using_permalinks() ) {
		add_action( 'admin_notices', 'correction_permalink_notice' );
	} else {
		add_shortcode( 'correction_link', 'correction_link_shortcode' );
		add_shortcode( 'correction_form', 'correction_form_shortcode' );
		// add_action('init', 'correction_form_handler');
		add_action( 'init', 'register_correction_post_type' );
		add_action( 'admin_menu', 'correction_admin_menu' );
		add_action( 'admin_init', 'handle_correction_actions' );
	}
}
add_action( 'init', 'correction_init', 5 );